diff options
Diffstat (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller')
46 files changed, 18167 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs new file mode 100644 index 00000000..9a452da1 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs | |||
@@ -0,0 +1,333 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Text; | ||
10 | using System.Diagnostics.CodeAnalysis; | ||
11 | |||
12 | /// <summary> | ||
13 | /// Collection of column information related to a <see cref="TableInfo"/> or | ||
14 | /// <see cref="View"/>. | ||
15 | /// </summary> | ||
16 | public sealed class ColumnCollection : ICollection<ColumnInfo> | ||
17 | { | ||
18 | private IList<ColumnInfo> columns; | ||
19 | private string formatString; | ||
20 | |||
21 | /// <summary> | ||
22 | /// Creates a new ColumnCollection based on a specified list of columns. | ||
23 | /// </summary> | ||
24 | /// <param name="columns">columns to be added to the new collection</param> | ||
25 | public ColumnCollection(ICollection<ColumnInfo> columns) | ||
26 | { | ||
27 | if (columns == null) | ||
28 | { | ||
29 | throw new ArgumentNullException("columns"); | ||
30 | } | ||
31 | |||
32 | this.columns = new List<ColumnInfo>(columns); | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Creates a new ColumnCollection that is associated with a database table. | ||
37 | /// </summary> | ||
38 | /// <param name="view">view that contains the columns</param> | ||
39 | internal ColumnCollection(View view) | ||
40 | { | ||
41 | if (view == null) | ||
42 | { | ||
43 | throw new ArgumentNullException("view"); | ||
44 | } | ||
45 | |||
46 | this.columns = ColumnCollection.GetViewColumns(view); | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Gets the number of columns in the collection. | ||
51 | /// </summary> | ||
52 | /// <value>number of columns in the collection</value> | ||
53 | public int Count | ||
54 | { | ||
55 | get | ||
56 | { | ||
57 | return this.columns.Count; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Gets a boolean value indicating whether the collection is read-only. | ||
63 | /// A ColumnCollection is read-only if it is associated with a <see cref="View"/> | ||
64 | /// or a read-only <see cref="Database"/>. | ||
65 | /// </summary> | ||
66 | /// <value>read-only status of the collection</value> | ||
67 | public bool IsReadOnly | ||
68 | { | ||
69 | get | ||
70 | { | ||
71 | return true; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | /// <summary> | ||
76 | /// Gets information about a specific column in the collection. | ||
77 | /// </summary> | ||
78 | /// <param name="columnIndex">1-based index into the column collection</param> | ||
79 | /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnIndex"/> is less | ||
80 | /// than 1 or greater than the number of columns in the collection</exception> | ||
81 | public ColumnInfo this[int columnIndex] | ||
82 | { | ||
83 | get | ||
84 | { | ||
85 | if (columnIndex >= 0 && columnIndex < this.columns.Count) | ||
86 | { | ||
87 | return this.columns[columnIndex]; | ||
88 | } | ||
89 | else | ||
90 | { | ||
91 | throw new ArgumentOutOfRangeException("columnIndex"); | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Gets information about a specific column in the collection. | ||
98 | /// </summary> | ||
99 | /// <param name="columnName">case-sensitive name of a column collection</param> | ||
100 | /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnName"/> does | ||
101 | /// not exist in the collection</exception> | ||
102 | public ColumnInfo this[string columnName] | ||
103 | { | ||
104 | get | ||
105 | { | ||
106 | if (String.IsNullOrEmpty(columnName)) | ||
107 | { | ||
108 | throw new ArgumentNullException("columnName"); | ||
109 | } | ||
110 | |||
111 | foreach (ColumnInfo colInfo in this.columns) | ||
112 | { | ||
113 | if (colInfo.Name == columnName) | ||
114 | { | ||
115 | return colInfo; | ||
116 | } | ||
117 | } | ||
118 | |||
119 | throw new ArgumentOutOfRangeException("columnName"); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Not supported because the collection is read-only. | ||
125 | /// </summary> | ||
126 | /// <param name="item">information about the column being added</param> | ||
127 | /// <exception cref="InvalidOperationException">the collection is read-only</exception> | ||
128 | public void Add(ColumnInfo item) | ||
129 | { | ||
130 | throw new InvalidOperationException(); | ||
131 | } | ||
132 | |||
133 | /// <summary> | ||
134 | /// Not supported because the collection is read-only. | ||
135 | /// </summary> | ||
136 | /// <exception cref="InvalidOperationException">the collection is read-only</exception> | ||
137 | public void Clear() | ||
138 | { | ||
139 | throw new InvalidOperationException(); | ||
140 | } | ||
141 | |||
142 | /// <summary> | ||
143 | /// Checks if a column with a given name exists in the collection. | ||
144 | /// </summary> | ||
145 | /// <param name="columnName">case-sensitive name of the column to look for</param> | ||
146 | /// <returns>true if the column exists in the collection, false otherwise</returns> | ||
147 | public bool Contains(string columnName) | ||
148 | { | ||
149 | return this.IndexOf(columnName) >= 0; | ||
150 | } | ||
151 | |||
152 | /// <summary> | ||
153 | /// Checks if a column with a given name exists in the collection. | ||
154 | /// </summary> | ||
155 | /// <param name="column">column to look for, with case-sensitive name</param> | ||
156 | /// <returns>true if the column exists in the collection, false otherwise</returns> | ||
157 | bool ICollection<ColumnInfo>.Contains(ColumnInfo column) | ||
158 | { | ||
159 | return this.Contains(column.Name); | ||
160 | } | ||
161 | |||
162 | /// <summary> | ||
163 | /// Gets the index of a column within the collection. | ||
164 | /// </summary> | ||
165 | /// <param name="columnName">case-sensitive name of the column to look for</param> | ||
166 | /// <returns>0-based index of the column, or -1 if not found</returns> | ||
167 | public int IndexOf(string columnName) | ||
168 | { | ||
169 | if (String.IsNullOrEmpty(columnName)) | ||
170 | { | ||
171 | throw new ArgumentNullException("columnName"); | ||
172 | } | ||
173 | |||
174 | for (int index = 0; index < this.columns.Count; index++) | ||
175 | { | ||
176 | if (this.columns[index].Name == columnName) | ||
177 | { | ||
178 | return index; | ||
179 | } | ||
180 | } | ||
181 | return -1; | ||
182 | } | ||
183 | |||
184 | /// <summary> | ||
185 | /// Copies the columns from this collection into an array. | ||
186 | /// </summary> | ||
187 | /// <param name="array">destination array to be filed</param> | ||
188 | /// <param name="arrayIndex">offset into the destination array where copying begins</param> | ||
189 | public void CopyTo(ColumnInfo[] array, int arrayIndex) | ||
190 | { | ||
191 | if (array == null) | ||
192 | { | ||
193 | throw new ArgumentNullException("array"); | ||
194 | } | ||
195 | |||
196 | this.columns.CopyTo(array, arrayIndex); | ||
197 | } | ||
198 | |||
199 | /// <summary> | ||
200 | /// Not supported because the collection is read-only. | ||
201 | /// </summary> | ||
202 | /// <param name="column">column to remove</param> | ||
203 | /// <returns>true if the column was removed, false if it was not found</returns> | ||
204 | /// <exception cref="InvalidOperationException">the collection is read-only</exception> | ||
205 | [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "column")] | ||
206 | [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] | ||
207 | bool ICollection<ColumnInfo>.Remove(ColumnInfo column) | ||
208 | { | ||
209 | throw new InvalidOperationException(); | ||
210 | } | ||
211 | |||
212 | /// <summary> | ||
213 | /// Gets an enumerator over the columns in the collection. | ||
214 | /// </summary> | ||
215 | /// <returns>An enumerator of ColumnInfo objects.</returns> | ||
216 | public IEnumerator<ColumnInfo> GetEnumerator() | ||
217 | { | ||
218 | return this.columns.GetEnumerator(); | ||
219 | } | ||
220 | |||
221 | /// <summary> | ||
222 | /// Gets a string suitable for printing all the values of a record containing these columns. | ||
223 | /// </summary> | ||
224 | public string FormatString | ||
225 | { | ||
226 | get | ||
227 | { | ||
228 | if (this.formatString == null) | ||
229 | { | ||
230 | this.formatString = CreateFormatString(this.columns); | ||
231 | } | ||
232 | return this.formatString; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | private static string CreateFormatString(IList<ColumnInfo> columns) | ||
237 | { | ||
238 | StringBuilder sb = new StringBuilder(); | ||
239 | for (int i = 0; i < columns.Count; i++) | ||
240 | { | ||
241 | if (columns[i].Type == typeof(Stream)) | ||
242 | { | ||
243 | sb.AppendFormat("{0} = [Binary Data]", columns[i].Name); | ||
244 | } | ||
245 | else | ||
246 | { | ||
247 | sb.AppendFormat("{0} = [{1}]", columns[i].Name, i + 1); | ||
248 | } | ||
249 | |||
250 | if (i < columns.Count - 1) | ||
251 | { | ||
252 | sb.Append(", "); | ||
253 | } | ||
254 | } | ||
255 | return sb.ToString(); | ||
256 | } | ||
257 | |||
258 | /// <summary> | ||
259 | /// Gets an enumerator over the columns in the collection. | ||
260 | /// </summary> | ||
261 | /// <returns>An enumerator of ColumnInfo objects.</returns> | ||
262 | IEnumerator IEnumerable.GetEnumerator() | ||
263 | { | ||
264 | return this.GetEnumerator(); | ||
265 | } | ||
266 | |||
267 | /// <summary> | ||
268 | /// Creates ColumnInfo objects for the associated view. | ||
269 | /// </summary> | ||
270 | /// <returns>dynamically-generated list of columns</returns> | ||
271 | private static IList<ColumnInfo> GetViewColumns(View view) | ||
272 | { | ||
273 | IList<string> columnNames = ColumnCollection.GetViewColumns(view, false); | ||
274 | IList<string> columnTypes = ColumnCollection.GetViewColumns(view, true); | ||
275 | |||
276 | int count = columnNames.Count; | ||
277 | if (columnTypes[count - 1] == "O0") | ||
278 | { | ||
279 | // Weird.. the "_Tables" table returns a second column with type "O0" -- ignore it. | ||
280 | count--; | ||
281 | } | ||
282 | |||
283 | IList<ColumnInfo> columnsList = new List<ColumnInfo>(count); | ||
284 | for (int i = 0; i < count; i++) | ||
285 | { | ||
286 | columnsList.Add(new ColumnInfo(columnNames[i], columnTypes[i])); | ||
287 | } | ||
288 | |||
289 | return columnsList; | ||
290 | } | ||
291 | |||
292 | /// <summary> | ||
293 | /// Gets a list of column names or column-definition-strings for the | ||
294 | /// associated view. | ||
295 | /// </summary> | ||
296 | /// <param name="view">the view to that defines the columns</param> | ||
297 | /// <param name="types">true to return types (column definition strings), | ||
298 | /// false to return names</param> | ||
299 | /// <returns>list of column names or types</returns> | ||
300 | private static IList<string> GetViewColumns(View view, bool types) | ||
301 | { | ||
302 | int recordHandle; | ||
303 | int typesFlag = types ? 1 : 0; | ||
304 | uint ret = RemotableNativeMethods.MsiViewGetColumnInfo( | ||
305 | (int) view.Handle, (uint) typesFlag, out recordHandle); | ||
306 | if (ret != 0) | ||
307 | { | ||
308 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
309 | } | ||
310 | |||
311 | using (Record rec = new Record((IntPtr) recordHandle, true, null)) | ||
312 | { | ||
313 | int count = rec.FieldCount; | ||
314 | IList<string> columnsList = new List<string>(count); | ||
315 | |||
316 | // Since we must be getting all strings of limited length, | ||
317 | // this code is faster than calling rec.GetString(field). | ||
318 | for (int field = 1; field <= count; field++) | ||
319 | { | ||
320 | uint bufSize = 256; | ||
321 | StringBuilder buf = new StringBuilder((int) bufSize); | ||
322 | ret = RemotableNativeMethods.MsiRecordGetString((int) rec.Handle, (uint) field, buf, ref bufSize); | ||
323 | if (ret != 0) | ||
324 | { | ||
325 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
326 | } | ||
327 | columnsList.Add(buf.ToString()); | ||
328 | } | ||
329 | return columnsList; | ||
330 | } | ||
331 | } | ||
332 | } | ||
333 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs new file mode 100644 index 00000000..ad0a945b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs | |||
@@ -0,0 +1,689 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics.CodeAnalysis; | ||
7 | |||
8 | // Enumerations are in alphabetical order. | ||
9 | |||
10 | /// <summary> | ||
11 | /// Available values for the Attributes column of the Component table. | ||
12 | /// </summary> | ||
13 | [Flags] | ||
14 | public enum ComponentAttributes : int | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// Local only - Component cannot be run from source. | ||
18 | /// </summary> | ||
19 | /// <remarks><p> | ||
20 | /// Set this value for all components belonging to a feature to prevent the feature from being run-from-network or | ||
21 | /// run-from-source. Note that if a feature has no components, the feature always shows run-from-source and | ||
22 | /// run-from-my-computer as valid options. | ||
23 | /// </p></remarks> | ||
24 | None = 0x0000, | ||
25 | |||
26 | /// <summary> | ||
27 | /// Component can only be run from source. | ||
28 | /// </summary> | ||
29 | /// <remarks><p> | ||
30 | /// Set this bit for all components belonging to a feature to prevent the feature from being run-from-my-computer. | ||
31 | /// Note that if a feature has no components, the feature always shows run-from-source and run-from-my-computer | ||
32 | /// as valid options. | ||
33 | /// </p></remarks> | ||
34 | SourceOnly = 0x0001, | ||
35 | |||
36 | /// <summary> | ||
37 | /// Component can run locally or from source. | ||
38 | /// </summary> | ||
39 | Optional = 0x0002, | ||
40 | |||
41 | /// <summary> | ||
42 | /// If this bit is set, the value in the KeyPath column is used as a key into the Registry table. | ||
43 | /// </summary> | ||
44 | /// <remarks><p> | ||
45 | /// If the Value field of the corresponding record in the Registry table is null, the Name field in that record | ||
46 | /// must not contain "+", "-", or "*". For more information, see the description of the Name field in Registry | ||
47 | /// table. | ||
48 | /// <p>Setting this bit is recommended for registry entries written to the HKCU hive. This ensures the installer | ||
49 | /// writes the necessary HKCU registry entries when there are multiple users on the same machine.</p> | ||
50 | /// </p></remarks> | ||
51 | RegistryKeyPath = 0x0004, | ||
52 | |||
53 | /// <summary> | ||
54 | /// If this bit is set, the installer increments the reference count in the shared DLL registry of the component's | ||
55 | /// key file. If this bit is not set, the installer increments the reference count only if the reference count | ||
56 | /// already exists. | ||
57 | /// </summary> | ||
58 | SharedDllRefCount = 0x0008, | ||
59 | |||
60 | /// <summary> | ||
61 | /// If this bit is set, the installer does not remove the component during an uninstall. The installer registers | ||
62 | /// an extra system client for the component in the Windows Installer registry settings. | ||
63 | /// </summary> | ||
64 | Permanent = 0x0010, | ||
65 | |||
66 | /// <summary> | ||
67 | /// If this bit is set, the value in the KeyPath column is a key into the ODBCDataSource table. | ||
68 | /// </summary> | ||
69 | OdbcDataSource = 0x0020, | ||
70 | |||
71 | /// <summary> | ||
72 | /// If this bit is set, the installer reevaluates the value of the statement in the Condition column upon a reinstall. | ||
73 | /// If the value was previously False and has changed to true, the installer installs the component. If the value | ||
74 | /// was previously true and has changed to false, the installer removes the component even if the component has | ||
75 | /// other products as clients. | ||
76 | /// </summary> | ||
77 | Transitive = 0x0040, | ||
78 | |||
79 | /// <summary> | ||
80 | /// If this bit is set, the installer does not install or reinstall the component if a key path file or a key path | ||
81 | /// registry entry for the component already exists. The application does register itself as a client of the component. | ||
82 | /// </summary> | ||
83 | /// <remarks><p> | ||
84 | /// Use this flag only for components that are being registered by the Registry table. Do not use this flag for | ||
85 | /// components registered by the AppId, Class, Extension, ProgId, MIME, and Verb tables. | ||
86 | /// </p></remarks> | ||
87 | NeverOverwrite = 0x0080, | ||
88 | |||
89 | /// <summary> | ||
90 | /// Set this bit to mark this as a 64-bit component. This attribute facilitates the installation of packages that | ||
91 | /// include both 32-bit and 64-bit components. If this bit is not set, the component is registered as a 32-bit component. | ||
92 | /// </summary> | ||
93 | /// <remarks><p> | ||
94 | /// If this is a 64-bit component replacing a 32-bit component, set this bit and assign a new GUID in the | ||
95 | /// ComponentId column. | ||
96 | /// </p></remarks> | ||
97 | SixtyFourBit = 0x0100, | ||
98 | |||
99 | /// <summary> | ||
100 | /// Set this bit to disable registry reflection on all existing and new registry keys affected by this component. | ||
101 | /// </summary> | ||
102 | /// <remarks><p> | ||
103 | /// If this bit is set, the Windows Installer calls the RegDisableReflectionKey on each key being accessed by the component. | ||
104 | /// This bit is available with Windows Installer version 4.0 and is ignored on 32-bit systems. | ||
105 | /// </p></remarks> | ||
106 | DisableRegistryReflection = 0x0200, | ||
107 | |||
108 | /// <summary> | ||
109 | /// [MSI 4.5] Set this bit for a component in a patch package to prevent leaving orphan components on the computer. | ||
110 | /// </summary> | ||
111 | /// <remarks><p> | ||
112 | /// If a subsequent patch is installed, marked with the SupersedeEarlier flag in its MsiPatchSequence | ||
113 | /// table to supersede the first patch, Windows Installer 4.5 can unregister and uninstall components marked with the | ||
114 | /// UninstallOnSupersedence value. If the component is not marked with this bit, installation of a superseding patch can leave | ||
115 | /// behind an unused component on the computer. | ||
116 | /// </p></remarks> | ||
117 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Supersedence")] | ||
118 | UninstallOnSupersedence = 0x0400, | ||
119 | |||
120 | /// <summary> | ||
121 | /// [MSI 4.5] If a component is marked with this attribute value in at least one package installed on the system, | ||
122 | /// the installer treats the component as marked in all packages. If a package that shares the marked component | ||
123 | /// is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system, | ||
124 | /// even if that highest version was installed by the package that is being uninstalled. | ||
125 | /// </summary> | ||
126 | Shared = 0x0800, | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Defines flags for the Attributes column of the Control table. | ||
131 | /// </summary> | ||
132 | [Flags] | ||
133 | public enum ControlAttributes : int | ||
134 | { | ||
135 | /// <summary>If this bit is set, the control is visible on the dialog box.</summary> | ||
136 | Visible = 0x00000001, | ||
137 | |||
138 | /// <summary>specifies if the given control is enabled or disabled. Most controls appear gray when disabled.</summary> | ||
139 | Enabled = 0x00000002, | ||
140 | |||
141 | /// <summary>If this bit is set, the control is displayed with a sunken, three dimensional look.</summary> | ||
142 | Sunken = 0x00000004, | ||
143 | |||
144 | /// <summary>The Indirect control attribute specifies whether the value displayed or changed by this control is referenced indirectly.</summary> | ||
145 | Indirect = 0x00000008, | ||
146 | |||
147 | /// <summary>If this bit is set on a control, the associated property specified in the Property column of the Control table is an integer.</summary> | ||
148 | Integer = 0x00000010, | ||
149 | |||
150 | /// <summary>If this bit is set the text in the control is displayed in a right-to-left reading order.</summary> | ||
151 | RightToLeftReadingOrder = 0x00000020, | ||
152 | |||
153 | /// <summary>If this style bit is set, text in the control is aligned to the right.</summary> | ||
154 | RightAligned = 0x00000040, | ||
155 | |||
156 | /// <summary>If this bit is set, the scroll bar is located on the left side of the control, otherwise it is on the right.</summary> | ||
157 | LeftScroll = 0x00000080, | ||
158 | |||
159 | /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and LeftScroll attributes.</summary> | ||
160 | Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll, | ||
161 | |||
162 | /// <summary>If this bit is set on a text control, the control is displayed transparently with the background showing through the control where there are no characters.</summary> | ||
163 | Transparent = 0x00010000, | ||
164 | |||
165 | /// <summary>If this bit is set on a text control, the occurrence of the character "&" in a text string is displayed as itself.</summary> | ||
166 | NoPrefix = 0x00020000, | ||
167 | |||
168 | /// <summary>If this bit is set the text in the control is displayed on a single line.</summary> | ||
169 | NoWrap = 0x00040000, | ||
170 | |||
171 | /// <summary>If this bit is set for a text control, the control will automatically attempt to format the displayed text as a number representing a count of bytes.</summary> | ||
172 | FormatSize = 0x00080000, | ||
173 | |||
174 | /// <summary>If this bit is set, fonts are created using the user's default UI code page. Otherwise it is created using the database code page.</summary> | ||
175 | UsersLanguage = 0x00100000, | ||
176 | |||
177 | /// <summary>If this bit is set on an Edit control, the installer creates a multiple line edit control with a vertical scroll bar.</summary> | ||
178 | Multiline = 0x00010000, | ||
179 | |||
180 | /// <summary>This attribute creates an edit control for entering passwords. The control displays each character as an asterisk (*) as they are typed into the control.</summary> | ||
181 | PasswordInput = 0x00200000, | ||
182 | |||
183 | /// <summary>If this bit is set on a ProgressBar control, the bar is drawn as a series of small rectangles in Microsoft Windows 95-style. Otherwise it is drawn as a single continuous rectangle.</summary> | ||
184 | Progress95 = 0x00010000, | ||
185 | |||
186 | /// <summary>If this bit is set, the control shows removable volumes.</summary> | ||
187 | RemovableVolume = 0x00010000, | ||
188 | |||
189 | /// <summary>If this bit is set, the control shows fixed internal hard drives.</summary> | ||
190 | FixedVolume = 0x00020000, | ||
191 | |||
192 | /// <summary>If this bit is set, the control shows remote volumes.</summary> | ||
193 | RemoteVolume = 0x00040000, | ||
194 | |||
195 | /// <summary>If this bit is set, the control shows CD-ROM volumes.</summary> | ||
196 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cdrom")] | ||
197 | CdromVolume = 0x00080000, | ||
198 | |||
199 | /// <summary>If this bit is set, the control shows RAM disk volumes.</summary> | ||
200 | RamDiskVolume = 0x00100000, | ||
201 | |||
202 | /// <summary>If this bit is set, the control shows floppy volumes.</summary> | ||
203 | FloppyVolume = 0x00200000, | ||
204 | |||
205 | /// <summary>Specifies whether or not the rollback backup files are included in the costs displayed by the VolumeCostList control.</summary> | ||
206 | ShowRollbackCost = 0x00400000, | ||
207 | |||
208 | /// <summary>If this bit is set, the items listed in the control are displayed in a specified order. Otherwise, items are displayed in alphabetical order.</summary> | ||
209 | Sorted = 0x00010000, | ||
210 | |||
211 | /// <summary>If this bit is set on a combo box, the edit field is replaced by a static text field. This prevents a user from entering a new value and requires the user to choose only one of the predefined values.</summary> | ||
212 | ComboList = 0x00020000, | ||
213 | |||
214 | //ImageHandle = 0x00010000, | ||
215 | |||
216 | /// <summary>If this bit is set on a check box or a radio button group, the button is drawn with the appearance of a push button, but its logic stays the same.</summary> | ||
217 | PushLike = 0x00020000, | ||
218 | |||
219 | /// <summary>If this bit is set, the text in the control is replaced by a bitmap image. The Text column in the Control table is a foreign key into the Binary table.</summary> | ||
220 | Bitmap = 0x00040000, | ||
221 | |||
222 | /// <summary>If this bit is set, text is replaced by an icon image and the Text column in the Control table is a foreign key into the Binary table.</summary> | ||
223 | Icon = 0x00080000, | ||
224 | |||
225 | /// <summary>If this bit is set, the picture is cropped or centered in the control without changing its shape or size.</summary> | ||
226 | FixedSize = 0x00100000, | ||
227 | |||
228 | /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary> | ||
229 | IconSize16 = 0x00200000, | ||
230 | |||
231 | /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary> | ||
232 | IconSize32 = 0x00400000, | ||
233 | |||
234 | /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary> | ||
235 | IconSize48 = 0x00600000, | ||
236 | |||
237 | /// <summary>If this bit is set, and the installation is not yet running with elevated privileges, the control is created with a UAC icon.</summary> | ||
238 | ElevationShield = 0x00800000, | ||
239 | |||
240 | /// <summary>If this bit is set, the RadioButtonGroup has text and a border displayed around it.</summary> | ||
241 | HasBorder = 0x01000000, | ||
242 | } | ||
243 | |||
244 | /// <summary> | ||
245 | /// Defines flags for the Type column of the CustomAction table. | ||
246 | /// </summary> | ||
247 | [SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags")] | ||
248 | [Flags] | ||
249 | public enum CustomActionTypes : int | ||
250 | { | ||
251 | /// <summary>Unspecified custom action type.</summary> | ||
252 | None = 0x0000, | ||
253 | |||
254 | /// <summary>Target = entry point name</summary> | ||
255 | Dll = 0x0001, | ||
256 | |||
257 | /// <summary>Target = command line args</summary> | ||
258 | Exe = 0x0002, | ||
259 | |||
260 | /// <summary>Target = text string to be formatted and set into property</summary> | ||
261 | TextData = 0x0003, | ||
262 | |||
263 | /// <summary>Target = entry point name, null if none to call</summary> | ||
264 | JScript = 0x0005, | ||
265 | |||
266 | /// <summary>Target = entry point name, null if none to call</summary> | ||
267 | VBScript = 0x0006, | ||
268 | |||
269 | /// <summary>Target = property list for nested engine initialization</summary> | ||
270 | Install = 0x0007, | ||
271 | |||
272 | /// <summary>Source = File.File, file part of installation</summary> | ||
273 | SourceFile = 0x0010, | ||
274 | |||
275 | /// <summary>Source = Directory.Directory, folder containing existing file</summary> | ||
276 | Directory = 0x0020, | ||
277 | |||
278 | /// <summary>Source = Property.Property, full path to executable</summary> | ||
279 | Property = 0x0030, | ||
280 | |||
281 | /// <summary>Ignore action return status, continue running</summary> | ||
282 | Continue = 0x0040, | ||
283 | |||
284 | /// <summary>Run asynchronously</summary> | ||
285 | Async = 0x0080, | ||
286 | |||
287 | /// <summary>Skip if UI sequence already run</summary> | ||
288 | FirstSequence = 0x0100, | ||
289 | |||
290 | /// <summary>Skip if UI sequence already run in same process</summary> | ||
291 | OncePerProcess = 0x0200, | ||
292 | |||
293 | /// <summary>Run on client only if UI already run on client</summary> | ||
294 | ClientRepeat = 0x0300, | ||
295 | |||
296 | /// <summary>Queue for execution within script</summary> | ||
297 | InScript = 0x0400, | ||
298 | |||
299 | /// <summary>In conjunction with InScript: queue in Rollback script</summary> | ||
300 | Rollback = 0x0100, | ||
301 | |||
302 | /// <summary>In conjunction with InScript: run Commit ops from script on success</summary> | ||
303 | Commit = 0x0200, | ||
304 | |||
305 | /// <summary>No impersonation, run in system context</summary> | ||
306 | NoImpersonate = 0x0800, | ||
307 | |||
308 | /// <summary>Impersonate for per-machine installs on TS machines</summary> | ||
309 | TSAware = 0x4000, | ||
310 | |||
311 | /// <summary>Script requires 64bit process</summary> | ||
312 | SixtyFourBitScript = 0x1000, | ||
313 | |||
314 | /// <summary>Don't record the contents of the Target field in the log file</summary> | ||
315 | HideTarget = 0x2000, | ||
316 | |||
317 | /// <summary>The custom action runs only when a patch is being uninstalled</summary> | ||
318 | PatchUninstall = 0x8000, | ||
319 | } | ||
320 | |||
321 | /// <summary> | ||
322 | /// Defines flags for the Attributes column of the Dialog table. | ||
323 | /// </summary> | ||
324 | [Flags] | ||
325 | public enum DialogAttributes : int | ||
326 | { | ||
327 | /// <summary>If this bit is set, the dialog is originally created as visible, otherwise it is hidden.</summary> | ||
328 | Visible = 0x00000001, | ||
329 | |||
330 | /// <summary>If this bit is set, the dialog box is modal, other dialogs of the same application cannot be put on top of it, and the dialog keeps the control while it is running.</summary> | ||
331 | Modal = 0x00000002, | ||
332 | |||
333 | /// <summary>If this bit is set, the dialog box can be minimized. This bit is ignored for modal dialog boxes, which cannot be minimized.</summary> | ||
334 | Minimize = 0x00000004, | ||
335 | |||
336 | /// <summary>If this style bit is set, the dialog box will stop all other applications and no other applications can take the focus.</summary> | ||
337 | SysModal = 0x00000008, | ||
338 | |||
339 | /// <summary>If this bit is set, the other dialogs stay alive when this dialog box is created.</summary> | ||
340 | KeepModeless = 0x00000010, | ||
341 | |||
342 | /// <summary>If this bit is set, the dialog box periodically calls the installer. If the property changes, it notifies the controls on the dialog.</summary> | ||
343 | TrackDiskSpace = 0x00000020, | ||
344 | |||
345 | /// <summary>If this bit is set, the pictures on the dialog box are created with the custom palette (one per dialog received from the first control created).</summary> | ||
346 | UseCustomPalette = 0x00000040, | ||
347 | |||
348 | /// <summary>If this style bit is set the text in the dialog box is displayed in right-to-left-reading order.</summary> | ||
349 | RightToLeftReadingOrder = 0x00000080, | ||
350 | |||
351 | /// <summary>If this style bit is set, the text is aligned on the right side of the dialog box.</summary> | ||
352 | RightAligned = 0x00000100, | ||
353 | |||
354 | /// <summary>If this style bit is set, the scroll bar is located on the left side of the dialog box.</summary> | ||
355 | LeftScroll = 0x00000200, | ||
356 | |||
357 | /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and the LeftScroll dialog style bits.</summary> | ||
358 | Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll, | ||
359 | |||
360 | /// <summary>If this bit is set, the dialog box is an error dialog.</summary> | ||
361 | Error = 0x00010000, | ||
362 | } | ||
363 | |||
364 | /// <summary> | ||
365 | /// Available values for the Attributes column of the Feature table. | ||
366 | /// </summary> | ||
367 | [Flags] | ||
368 | public enum FeatureAttributes : int | ||
369 | { | ||
370 | /// <summary> | ||
371 | /// Favor local - Components of this feature that are not marked for installation from source are installed locally. | ||
372 | /// </summary> | ||
373 | /// <remarks><p> | ||
374 | /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource, | ||
375 | /// is installed locally. Components marked <see cref="ComponentAttributes.SourceOnly"/> in the Component | ||
376 | /// table are always run from the source CD/server. The bits FavorLocal and FavorSource work with features not | ||
377 | /// listed by the ADVERTISE property. | ||
378 | /// </p></remarks> | ||
379 | None = 0x0000, | ||
380 | |||
381 | /// <summary> | ||
382 | /// Components of this feature not marked for local installation are installed to run from the source | ||
383 | /// CD-ROM or server. | ||
384 | /// </summary> | ||
385 | /// <remarks><p> | ||
386 | /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource, | ||
387 | /// is installed to run locally. Components marked <see cref="ComponentAttributes.None"/> (local-only) in the | ||
388 | /// Component table are always installed locally. The bits FavorLocal and FavorSource work with features | ||
389 | /// not listed by the ADVERTISE property. | ||
390 | /// </p></remarks> | ||
391 | FavorSource = 0x0001, | ||
392 | |||
393 | /// <summary> | ||
394 | /// Set this attribute and the state of the feature is the same as the state of the feature's parent. | ||
395 | /// You cannot use this option if the feature is located at the root of a feature tree. | ||
396 | /// </summary> | ||
397 | /// <remarks><p> | ||
398 | /// Omit this attribute and the feature state is determined according to DisallowAdvertise and | ||
399 | /// FavorLocal and FavorSource. | ||
400 | /// <p>To guarantee that the child feature's state always follows the state of its parent, even when the | ||
401 | /// child and parent are initially set to absent in the SelectionTree control, you must include both | ||
402 | /// FollowParent and UIDisallowAbsent in the attributes of the child feature.</p> | ||
403 | /// <p>Note that if you set FollowParent without setting UIDisallowAbsent, the installer cannot force | ||
404 | /// the child feature out of the absent state. In this case, the child feature matches the parent's | ||
405 | /// installation state only if the child is set to something other than absent.</p> | ||
406 | /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p> | ||
407 | /// </p></remarks> | ||
408 | FollowParent = 0x0002, | ||
409 | |||
410 | /// <summary> | ||
411 | /// Set this attribute and the feature state is Advertise. | ||
412 | /// </summary> | ||
413 | /// <remarks><p> | ||
414 | /// If the feature is listed by the ADDDEFAULT property this bit is ignored and the feature state is determined | ||
415 | /// according to FavorLocal and FavorSource. | ||
416 | /// <p>Omit this attribute and the feature state is determined according to DisallowAdvertise and FavorLocal | ||
417 | /// and FavorSource.</p> | ||
418 | /// </p></remarks> | ||
419 | FavorAdvertise = 0x0004, | ||
420 | |||
421 | /// <summary> | ||
422 | /// Set this attribute to prevent the feature from being advertised. | ||
423 | /// </summary> | ||
424 | /// <remarks><p> | ||
425 | /// Note that this bit works only with features that are listed by the ADVERTISE property. | ||
426 | /// <p>Set this attribute and if the listed feature is not a parent or child, the feature is installed according to | ||
427 | /// FavorLocal and FavorSource.</p> | ||
428 | /// <p>Set this attribute for the parent of a listed feature and the parent is installed.</p> | ||
429 | /// <p>Set this attribute for the child of a listed feature and the state of the child is Absent.</p> | ||
430 | /// <p>Omit this attribute and if the listed feature is not a parent or child, the feature state is Advertise.</p> | ||
431 | /// <p>Omit this attribute and if the listed feature is a parent or child, the state of both features is Advertise.</p> | ||
432 | /// </p></remarks> | ||
433 | DisallowAdvertise = 0x0008, | ||
434 | |||
435 | /// <summary> | ||
436 | /// Set this attribute and the user interface does not display an option to change the feature state | ||
437 | /// to Absent. Setting this attribute forces the feature to the installation state, whether or not the | ||
438 | /// feature is visible in the UI. | ||
439 | /// </summary> | ||
440 | /// <remarks><p> | ||
441 | /// Omit this attribute and the user interface displays an option to change the feature state to Absent. | ||
442 | /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p> | ||
443 | /// <p>Setting this attribute not only affects the UI, but also forces the feature to the install state whether | ||
444 | /// the feature is visible in the UI or not.</p> | ||
445 | /// </p></remarks> | ||
446 | UIDisallowAbsent = 0x0010, | ||
447 | |||
448 | /// <summary> | ||
449 | /// Set this attribute and advertising is disabled for the feature if the operating system shell does not | ||
450 | /// support Windows Installer descriptors. | ||
451 | /// </summary> | ||
452 | NoUnsupportedAdvertise = 0x0020, | ||
453 | } | ||
454 | |||
455 | /// <summary> | ||
456 | /// Available values for the Attributes column of the File table. | ||
457 | /// </summary> | ||
458 | [Flags] | ||
459 | public enum FileAttributes : int | ||
460 | { | ||
461 | /// <summary>No attributes.</summary> | ||
462 | None = 0x0000, | ||
463 | |||
464 | /// <summary>Read-only.</summary> | ||
465 | ReadOnly = 0x0001, | ||
466 | |||
467 | /// <summary>Hidden.</summary> | ||
468 | Hidden = 0x0002, | ||
469 | |||
470 | /// <summary>System.</summary> | ||
471 | System = 0x0004, | ||
472 | |||
473 | /// <summary>The file is vital for the proper operation of the component to which it belongs.</summary> | ||
474 | Vital = 0x0200, | ||
475 | |||
476 | /// <summary>The file contains a valid checksum. A checksum is required to repair a file that has become corrupted.</summary> | ||
477 | Checksum = 0x0400, | ||
478 | |||
479 | /// <summary>This bit must only be added by a patch and if the file is being added by the patch.</summary> | ||
480 | PatchAdded = 0x1000, | ||
481 | |||
482 | /// <summary> | ||
483 | /// The file's source type is uncompressed. If set, ignore the WordCount summary information property. If neither | ||
484 | /// Noncompressed nor Compressed are set, the compression state of the file is specified by the WordCount summary | ||
485 | /// information property. Do not set both Noncompressed and Compressed. | ||
486 | /// </summary> | ||
487 | NonCompressed = 0x2000, | ||
488 | |||
489 | /// <summary> | ||
490 | /// The file's source type is compressed. If set, ignore the WordCount summary information property. If neither | ||
491 | /// Noncompressed or Compressed are set, the compression state of the file is specified by the WordCount summary | ||
492 | /// information property. Do not set both Noncompressed and Compressed. | ||
493 | /// </summary> | ||
494 | Compressed = 0x4000, | ||
495 | } | ||
496 | |||
497 | /// <summary> | ||
498 | /// Defines values for the Action column of the IniFile and RemoveIniFile tables. | ||
499 | /// </summary> | ||
500 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ini")] | ||
501 | public enum IniFileAction : int | ||
502 | { | ||
503 | /// <summary>Creates or updates a .ini entry.</summary> | ||
504 | AddLine = 0, | ||
505 | |||
506 | /// <summary>Creates a .ini entry only if the entry does not already exist.</summary> | ||
507 | CreateLine = 1, | ||
508 | |||
509 | /// <summary>Deletes .ini entry.</summary> | ||
510 | RemoveLine = 2, | ||
511 | |||
512 | /// <summary>Creates a new entry or appends a new comma-separated value to an existing entry.</summary> | ||
513 | AddTag = 3, | ||
514 | |||
515 | /// <summary>Deletes a tag from a .ini entry.</summary> | ||
516 | RemoveTag = 4, | ||
517 | } | ||
518 | |||
519 | /// <summary> | ||
520 | /// Defines values for the Type column of the CompLocator, IniLocator, and RegLocator tables. | ||
521 | /// </summary> | ||
522 | [Flags] | ||
523 | [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] | ||
524 | public enum LocatorTypes : int | ||
525 | { | ||
526 | /// <summary>Key path is a directory.</summary> | ||
527 | Directory = 0x00000000, | ||
528 | |||
529 | /// <summary>Key path is a file name.</summary> | ||
530 | FileName = 0x00000001, | ||
531 | |||
532 | /// <summary>Key path is a registry value.</summary> | ||
533 | RawValue = 0x00000002, | ||
534 | |||
535 | /// <summary>Set this bit to have the installer search the 64-bit portion of the registry.</summary> | ||
536 | SixtyFourBit = 0x00000010, | ||
537 | } | ||
538 | |||
539 | /// <summary> | ||
540 | /// Defines values for the Root column of the Registry, RemoveRegistry, and RegLocator tables. | ||
541 | /// </summary> | ||
542 | public enum RegistryRoot : int | ||
543 | { | ||
544 | /// <summary>HKEY_CURRENT_USER for a per-user installation, | ||
545 | /// or HKEY_LOCAL_MACHINE for a per-machine installation.</summary> | ||
546 | UserOrMachine = -1, | ||
547 | |||
548 | /// <summary>HKEY_CLASSES_ROOT</summary> | ||
549 | ClassesRoot = 0, | ||
550 | |||
551 | /// <summary>HKEY_CURRENT_USER</summary> | ||
552 | CurrentUser = 1, | ||
553 | |||
554 | /// <summary>HKEY_LOCAL_MACHINE</summary> | ||
555 | LocalMachine = 2, | ||
556 | |||
557 | /// <summary>HKEY_USERS</summary> | ||
558 | Users = 3, | ||
559 | } | ||
560 | |||
561 | /// <summary> | ||
562 | /// Defines values for the InstallMode column of the RemoveFile table. | ||
563 | /// </summary> | ||
564 | [Flags] | ||
565 | public enum RemoveFileModes : int | ||
566 | { | ||
567 | /// <summary>Never remove.</summary> | ||
568 | None = 0, | ||
569 | |||
570 | /// <summary>Remove when the associated component is being installed (install state = local or source).</summary> | ||
571 | OnInstall = 1, | ||
572 | |||
573 | /// <summary>Remove when the associated component is being removed (install state = absent).</summary> | ||
574 | OnRemove = 2, | ||
575 | } | ||
576 | |||
577 | /// <summary> | ||
578 | /// Defines values for the ServiceType, StartType, and ErrorControl columns of the ServiceInstall table. | ||
579 | /// </summary> | ||
580 | [Flags] | ||
581 | public enum ServiceAttributes : int | ||
582 | { | ||
583 | /// <summary>No flags.</summary> | ||
584 | None = 0, | ||
585 | |||
586 | /// <summary>A Win32 service that runs its own process.</summary> | ||
587 | OwnProcess = 0x0010, | ||
588 | |||
589 | /// <summary>A Win32 service that shares a process.</summary> | ||
590 | ShareProcess = 0x0020, | ||
591 | |||
592 | /// <summary>A Win32 service that interacts with the desktop. | ||
593 | /// This value cannot be used alone and must be added to either | ||
594 | /// <see cref="OwnProcess"/> or <see cref="ShareProcess"/>.</summary> | ||
595 | Interactive = 0x0100, | ||
596 | |||
597 | /// <summary>Service starts during startup of the system.</summary> | ||
598 | AutoStart = 0x0002, | ||
599 | |||
600 | /// <summary>Service starts when the service control manager calls the StartService function.</summary> | ||
601 | DemandStart = 0x0003, | ||
602 | |||
603 | /// <summary>Specifies a service that can no longer be started.</summary> | ||
604 | Disabled = 0x0004, | ||
605 | |||
606 | /// <summary>Logs the error, displays a message box and continues the startup operation.</summary> | ||
607 | ErrorMessage = 0x0001, | ||
608 | |||
609 | /// <summary>Logs the error if it is possible and the system is restarted with the last configuration | ||
610 | /// known to be good. If the last-known-good configuration is being started, the startup operation fails.</summary> | ||
611 | ErrorCritical = 0x0003, | ||
612 | |||
613 | /// <summary>When combined with other error flags, specifies that the overall install should fail if | ||
614 | /// the service cannot be installed into the system.</summary> | ||
615 | ErrorControlVital = 0x8000, | ||
616 | } | ||
617 | |||
618 | /// <summary> | ||
619 | /// Defines values for the Event column of the ServiceControl table. | ||
620 | /// </summary> | ||
621 | [Flags] | ||
622 | public enum ServiceControlEvents : int | ||
623 | { | ||
624 | /// <summary>No control events.</summary> | ||
625 | None = 0x0000, | ||
626 | |||
627 | /// <summary>During an install, starts the service during the StartServices action.</summary> | ||
628 | Start = 0x0001, | ||
629 | |||
630 | /// <summary>During an install, stops the service during the StopServices action.</summary> | ||
631 | Stop = 0x0002, | ||
632 | |||
633 | /// <summary>During an install, deletes the service during the DeleteServices action.</summary> | ||
634 | Delete = 0x0008, | ||
635 | |||
636 | /// <summary>During an uninstall, starts the service during the StartServices action.</summary> | ||
637 | UninstallStart = 0x0010, | ||
638 | |||
639 | /// <summary>During an uninstall, stops the service during the StopServices action.</summary> | ||
640 | UninstallStop = 0x0020, | ||
641 | |||
642 | /// <summary>During an uninstall, deletes the service during the DeleteServices action.</summary> | ||
643 | UninstallDelete = 0x0080, | ||
644 | } | ||
645 | |||
646 | /// <summary> | ||
647 | /// Defines values for the StyleBits column of the TextStyle table. | ||
648 | /// </summary> | ||
649 | [Flags] | ||
650 | public enum TextStyles : int | ||
651 | { | ||
652 | /// <summary>Bold</summary> | ||
653 | Bold = 0x0001, | ||
654 | |||
655 | /// <summary>Italic</summary> | ||
656 | Italic = 0x0002, | ||
657 | |||
658 | /// <summary>Underline</summary> | ||
659 | Underline = 0x0004, | ||
660 | |||
661 | /// <summary>Strike out</summary> | ||
662 | Strike = 0x0008, | ||
663 | } | ||
664 | |||
665 | /// <summary> | ||
666 | /// Defines values for the Attributes column of the Upgrade table. | ||
667 | /// </summary> | ||
668 | [Flags] | ||
669 | public enum UpgradeAttributes : int | ||
670 | { | ||
671 | /// <summary>Migrates feature states by enabling the logic in the MigrateFeatureStates action.</summary> | ||
672 | MigrateFeatures = 0x0001, | ||
673 | |||
674 | /// <summary>Detects products and applications but does not remove.</summary> | ||
675 | OnlyDetect = 0x0002, | ||
676 | |||
677 | /// <summary>Continues installation upon failure to remove a product or application.</summary> | ||
678 | IgnoreRemoveFailure = 0x0004, | ||
679 | |||
680 | /// <summary>Detects the range of versions including the value in VersionMin.</summary> | ||
681 | VersionMinInclusive = 0x0100, | ||
682 | |||
683 | /// <summary>Dectects the range of versions including the value in VersionMax.</summary> | ||
684 | VersionMaxInclusive = 0x0200, | ||
685 | |||
686 | /// <summary>Detects all languages, excluding the languages listed in the Language column.</summary> | ||
687 | LanguagesExclusive = 0x0400, | ||
688 | } | ||
689 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs new file mode 100644 index 00000000..43363230 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs | |||
@@ -0,0 +1,297 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Defines a single column of a table in an installer database. | ||
13 | /// </summary> | ||
14 | /// <remarks>Once created, a ColumnInfo object is immutable.</remarks> | ||
15 | public class ColumnInfo | ||
16 | { | ||
17 | private string name; | ||
18 | private Type type; | ||
19 | private int size; | ||
20 | private bool isRequired; | ||
21 | private bool isTemporary; | ||
22 | private bool isLocalizable; | ||
23 | |||
24 | /// <summary> | ||
25 | /// Creates a new ColumnInfo object from a column definition. | ||
26 | /// </summary> | ||
27 | /// <param name="name">name of the column</param> | ||
28 | /// <param name="columnDefinition">column definition string</param> | ||
29 | /// <seealso cref="ColumnDefinitionString"/> | ||
30 | public ColumnInfo(string name, string columnDefinition) | ||
31 | : this(name, typeof(String), 0, false, false, false) | ||
32 | { | ||
33 | if (name == null) | ||
34 | { | ||
35 | throw new ArgumentNullException("name"); | ||
36 | } | ||
37 | |||
38 | if (columnDefinition == null) | ||
39 | { | ||
40 | throw new ArgumentNullException("columnDefinition"); | ||
41 | } | ||
42 | |||
43 | switch (Char.ToLower(columnDefinition[0], CultureInfo.InvariantCulture)) | ||
44 | { | ||
45 | case 'i': this.type = typeof(Int32); | ||
46 | break; | ||
47 | case 'j': this.type = typeof(Int32); this.isTemporary = true; | ||
48 | break; | ||
49 | case 'g': this.type = typeof(String); this.isTemporary = true; | ||
50 | break; | ||
51 | case 'l': this.type = typeof(String); this.isLocalizable = true; | ||
52 | break; | ||
53 | case 'o': this.type = typeof(Stream); this.isTemporary = true; | ||
54 | break; | ||
55 | case 's': this.type = typeof(String); | ||
56 | break; | ||
57 | case 'v': this.type = typeof(Stream); | ||
58 | break; | ||
59 | default: throw new InstallerException(); | ||
60 | } | ||
61 | |||
62 | this.isRequired = Char.IsLower(columnDefinition[0]); | ||
63 | this.size = Int32.Parse( | ||
64 | columnDefinition.Substring(1), | ||
65 | CultureInfo.InvariantCulture.NumberFormat); | ||
66 | if (this.type == typeof(Int32) && this.size <= 2) | ||
67 | { | ||
68 | this.type = typeof(Int16); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | /// <summary> | ||
73 | /// Creates a new ColumnInfo object from a list of parameters. | ||
74 | /// </summary> | ||
75 | /// <param name="name">name of the column</param> | ||
76 | /// <param name="type">type of the column; must be one of the following: | ||
77 | /// Int16, Int32, String, or Stream</param> | ||
78 | /// <param name="size">the maximum number of characters for String columns; | ||
79 | /// ignored for other column types</param> | ||
80 | /// <param name="isRequired">true if the column is required to have a non-null value</param> | ||
81 | public ColumnInfo(string name, Type type, int size, bool isRequired) | ||
82 | : this(name, type, size, isRequired, false, false) | ||
83 | { | ||
84 | } | ||
85 | |||
86 | /// <summary> | ||
87 | /// Creates a new ColumnInfo object from a list of parameters. | ||
88 | /// </summary> | ||
89 | /// <param name="name">name of the column</param> | ||
90 | /// <param name="type">type of the column; must be one of the following: | ||
91 | /// Int16, Int32, String, or Stream</param> | ||
92 | /// <param name="size">the maximum number of characters for String columns; | ||
93 | /// ignored for other column types</param> | ||
94 | /// <param name="isRequired">true if the column is required to have a non-null value</param> | ||
95 | /// <param name="isTemporary">true to if the column is only in-memory and | ||
96 | /// not persisted with the database</param> | ||
97 | /// <param name="isLocalizable">for String columns, indicates the column | ||
98 | /// is localizable; ignored for other column types</param> | ||
99 | public ColumnInfo(string name, Type type, int size, bool isRequired, bool isTemporary, bool isLocalizable) | ||
100 | { | ||
101 | if (name == null) | ||
102 | { | ||
103 | throw new ArgumentNullException("name"); | ||
104 | } | ||
105 | |||
106 | if (type == typeof(Int32)) | ||
107 | { | ||
108 | size = 4; | ||
109 | isLocalizable = false; | ||
110 | } | ||
111 | else if (type == typeof(Int16)) | ||
112 | { | ||
113 | size = 2; | ||
114 | isLocalizable = false; | ||
115 | } | ||
116 | else if (type == typeof(String)) | ||
117 | { | ||
118 | } | ||
119 | else if (type == typeof(Stream)) | ||
120 | { | ||
121 | isLocalizable = false; | ||
122 | } | ||
123 | else | ||
124 | { | ||
125 | throw new ArgumentOutOfRangeException("type"); | ||
126 | } | ||
127 | |||
128 | this.name = name; | ||
129 | this.type = type; | ||
130 | this.size = size; | ||
131 | this.isRequired = isRequired; | ||
132 | this.isTemporary = isTemporary; | ||
133 | this.isLocalizable = isLocalizable; | ||
134 | } | ||
135 | |||
136 | /// <summary> | ||
137 | /// Gets the name of the column. | ||
138 | /// </summary> | ||
139 | /// <value>name of the column</value> | ||
140 | public string Name | ||
141 | { | ||
142 | get { return this.name; } | ||
143 | } | ||
144 | |||
145 | /// <summary> | ||
146 | /// Gets the type of the column as a System.Type. This is one of the following: Int16, Int32, String, or Stream | ||
147 | /// </summary> | ||
148 | /// <value>type of the column</value> | ||
149 | [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] | ||
150 | public Type Type | ||
151 | { | ||
152 | get { return this.type; } | ||
153 | } | ||
154 | |||
155 | /// <summary> | ||
156 | /// Gets the type of the column as an integer that can be cast to a System.Data.DbType. This is one of the following: Int16, Int32, String, or Binary | ||
157 | /// </summary> | ||
158 | /// <value>equivalent DbType of the column as an integer</value> | ||
159 | public int DBType | ||
160 | { | ||
161 | get | ||
162 | { | ||
163 | if (this.type == typeof(Int16)) return 10; | ||
164 | else if (this.type == typeof(Int32)) return 11; | ||
165 | else if (this.type == typeof(Stream)) return 1; | ||
166 | else return 16; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | /// <summary> | ||
171 | /// Gets the size of the column. | ||
172 | /// </summary> | ||
173 | /// <value>The size of integer columns this is either 2 or 4. For string columns this is the maximum | ||
174 | /// recommended length of the string, or 0 for unlimited length. For stream columns, 0 is returned.</value> | ||
175 | public int Size | ||
176 | { | ||
177 | get { return this.size; } | ||
178 | } | ||
179 | |||
180 | /// <summary> | ||
181 | /// Gets a value indicating whether the column must be non-null when inserting a record. | ||
182 | /// </summary> | ||
183 | /// <value>required status of the column</value> | ||
184 | public bool IsRequired | ||
185 | { | ||
186 | get { return this.isRequired; } | ||
187 | } | ||
188 | |||
189 | /// <summary> | ||
190 | /// Gets a value indicating whether the column is temporary. Temporary columns are not persisted | ||
191 | /// when the database is saved to disk. | ||
192 | /// </summary> | ||
193 | /// <value>temporary status of the column</value> | ||
194 | public bool IsTemporary | ||
195 | { | ||
196 | get { return this.isTemporary; } | ||
197 | } | ||
198 | |||
199 | /// <summary> | ||
200 | /// Gets a value indicating whether the column is a string column that is localizable. | ||
201 | /// </summary> | ||
202 | /// <value>localizable status of the column</value> | ||
203 | public bool IsLocalizable | ||
204 | { | ||
205 | get { return this.isLocalizable; } | ||
206 | } | ||
207 | |||
208 | /// <summary> | ||
209 | /// Gets an SQL fragment that can be used to create this column within a CREATE TABLE statement. | ||
210 | /// </summary> | ||
211 | /// <value>SQL fragment to be used for creating the column</value> | ||
212 | /// <remarks><p> | ||
213 | /// Examples: | ||
214 | /// <list type="bullet"> | ||
215 | /// <item>LONG</item> | ||
216 | /// <item>SHORT TEMPORARY</item> | ||
217 | /// <item>CHAR(0) LOCALIZABLE</item> | ||
218 | /// <item>CHAR(72) NOT NULL LOCALIZABLE</item> | ||
219 | /// <item>OBJECT</item> | ||
220 | /// </list> | ||
221 | /// </p></remarks> | ||
222 | public string SqlCreateString | ||
223 | { | ||
224 | get | ||
225 | { | ||
226 | StringBuilder s = new StringBuilder(); | ||
227 | s.AppendFormat("`{0}` ", this.name); | ||
228 | if (this.type == typeof(Int16)) s.Append("SHORT"); | ||
229 | else if (this.type == typeof(Int32)) s.Append("LONG"); | ||
230 | else if (this.type == typeof(String)) s.AppendFormat("CHAR({0})", this.size); | ||
231 | else s.Append("OBJECT"); | ||
232 | if (this.isRequired) s.Append(" NOT NULL"); | ||
233 | if (this.isTemporary) s.Append(" TEMPORARY"); | ||
234 | if (this.isLocalizable) s.Append(" LOCALIZABLE"); | ||
235 | return s.ToString(); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /// <summary> | ||
240 | /// Gets a short string defining the type and size of the column. | ||
241 | /// </summary> | ||
242 | /// <value> | ||
243 | /// The definition string consists | ||
244 | /// of a single letter representing the data type followed by the width of the column (in characters | ||
245 | /// when applicable, bytes otherwise). A width of zero designates an unbounded width (for example, | ||
246 | /// long text fields and streams). An uppercase letter indicates that null values are allowed in | ||
247 | /// the column. | ||
248 | /// </value> | ||
249 | /// <remarks><p> | ||
250 | /// <list> | ||
251 | /// <item>s? - String, variable length (?=1-255)</item> | ||
252 | /// <item>s0 - String, variable length</item> | ||
253 | /// <item>i2 - Short integer</item> | ||
254 | /// <item>i4 - Long integer</item> | ||
255 | /// <item>v0 - Binary Stream</item> | ||
256 | /// <item>g? - Temporary string (?=0-255)</item> | ||
257 | /// <item>j? - Temporary integer (?=0,1,2,4)</item> | ||
258 | /// <item>O0 - Temporary object (stream)</item> | ||
259 | /// <item>l? - Localizable string, variable length (?=1-255)</item> | ||
260 | /// <item>l0 - Localizable string, variable length</item> | ||
261 | /// </list> | ||
262 | /// </p></remarks> | ||
263 | public string ColumnDefinitionString | ||
264 | { | ||
265 | get | ||
266 | { | ||
267 | char t; | ||
268 | if (this.type == typeof(Int16) || this.type == typeof(Int32)) | ||
269 | { | ||
270 | t = (this.isTemporary ? 'j' : 'i'); | ||
271 | } | ||
272 | else if (this.type == typeof(String)) | ||
273 | { | ||
274 | t = (this.isTemporary ? 'g' : this.isLocalizable ? 'l' : 's'); | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | t = (this.isTemporary ? 'O' : 'v'); | ||
279 | } | ||
280 | return String.Format( | ||
281 | CultureInfo.InvariantCulture, | ||
282 | "{0}{1}", | ||
283 | (this.isRequired ? t : Char.ToUpper(t, CultureInfo.InvariantCulture)), | ||
284 | this.size); | ||
285 | } | ||
286 | } | ||
287 | |||
288 | /// <summary> | ||
289 | /// Gets the name of the column. | ||
290 | /// </summary> | ||
291 | /// <returns>Name of the column.</returns> | ||
292 | public override string ToString() | ||
293 | { | ||
294 | return this.Name; | ||
295 | } | ||
296 | } | ||
297 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs new file mode 100644 index 00000000..af27fd1d --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs | |||
@@ -0,0 +1,276 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections; | ||
8 | using System.Collections.Generic; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Accessor for information about components within the context of an installation session. | ||
12 | /// </summary> | ||
13 | public sealed class ComponentInfoCollection : ICollection<ComponentInfo> | ||
14 | { | ||
15 | private Session session; | ||
16 | |||
17 | internal ComponentInfoCollection(Session session) | ||
18 | { | ||
19 | this.session = session; | ||
20 | } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Gets information about a component within the context of an installation session. | ||
24 | /// </summary> | ||
25 | /// <param name="component">name of the component</param> | ||
26 | /// <returns>component object</returns> | ||
27 | public ComponentInfo this[string component] | ||
28 | { | ||
29 | get | ||
30 | { | ||
31 | return new ComponentInfo(this.session, component); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | void ICollection<ComponentInfo>.Add(ComponentInfo item) | ||
36 | { | ||
37 | throw new InvalidOperationException(); | ||
38 | } | ||
39 | |||
40 | void ICollection<ComponentInfo>.Clear() | ||
41 | { | ||
42 | throw new InvalidOperationException(); | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Checks if the collection contains a component. | ||
47 | /// </summary> | ||
48 | /// <param name="component">name of the component</param> | ||
49 | /// <returns>true if the component is in the collection, else false</returns> | ||
50 | public bool Contains(string component) | ||
51 | { | ||
52 | return this.session.Database.CountRows( | ||
53 | "Component", "`Component` = '" + component + "'") == 1; | ||
54 | } | ||
55 | |||
56 | bool ICollection<ComponentInfo>.Contains(ComponentInfo item) | ||
57 | { | ||
58 | return item != null && this.Contains(item.Name); | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Copies the features into an array. | ||
63 | /// </summary> | ||
64 | /// <param name="array">array that receives the features</param> | ||
65 | /// <param name="arrayIndex">offset into the array</param> | ||
66 | public void CopyTo(ComponentInfo[] array, int arrayIndex) | ||
67 | { | ||
68 | foreach (ComponentInfo component in this) | ||
69 | { | ||
70 | array[arrayIndex++] = component; | ||
71 | } | ||
72 | } | ||
73 | |||
74 | /// <summary> | ||
75 | /// Gets the number of components defined for the product. | ||
76 | /// </summary> | ||
77 | public int Count | ||
78 | { | ||
79 | get | ||
80 | { | ||
81 | return this.session.Database.CountRows("Component"); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | bool ICollection<ComponentInfo>.IsReadOnly | ||
86 | { | ||
87 | get | ||
88 | { | ||
89 | return true; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | bool ICollection<ComponentInfo>.Remove(ComponentInfo item) | ||
94 | { | ||
95 | throw new InvalidOperationException(); | ||
96 | } | ||
97 | |||
98 | /// <summary> | ||
99 | /// Enumerates the components in the collection. | ||
100 | /// </summary> | ||
101 | /// <returns>an enumerator over all features in the collection</returns> | ||
102 | public IEnumerator<ComponentInfo> GetEnumerator() | ||
103 | { | ||
104 | using (View compView = this.session.Database.OpenView( | ||
105 | "SELECT `Component` FROM `Component`")) | ||
106 | { | ||
107 | compView.Execute(); | ||
108 | |||
109 | foreach (Record compRec in compView) using (compRec) | ||
110 | { | ||
111 | string comp = compRec.GetString(1); | ||
112 | yield return new ComponentInfo(this.session, comp); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | IEnumerator IEnumerable.GetEnumerator() | ||
118 | { | ||
119 | return this.GetEnumerator(); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Provides access to information about a component within the context of an installation session. | ||
125 | /// </summary> | ||
126 | public class ComponentInfo | ||
127 | { | ||
128 | private Session session; | ||
129 | private string name; | ||
130 | |||
131 | internal ComponentInfo(Session session, string name) | ||
132 | { | ||
133 | this.session = session; | ||
134 | this.name = name; | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Gets the name of the component (primary key in the Component table). | ||
139 | /// </summary> | ||
140 | public string Name | ||
141 | { | ||
142 | get | ||
143 | { | ||
144 | return this.name; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Gets the current install state of the designated Component. | ||
150 | /// </summary> | ||
151 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
152 | /// <exception cref="ArgumentException">an unknown Component was requested</exception> | ||
153 | /// <remarks><p> | ||
154 | /// Win32 MSI API: | ||
155 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a> | ||
156 | /// </p></remarks> | ||
157 | public InstallState CurrentState | ||
158 | { | ||
159 | get | ||
160 | { | ||
161 | int installedState, actionState; | ||
162 | uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState); | ||
163 | if (ret != 0) | ||
164 | { | ||
165 | if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) | ||
166 | { | ||
167 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
168 | } | ||
169 | else | ||
170 | { | ||
171 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
172 | } | ||
173 | } | ||
174 | return (InstallState) installedState; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /// <summary> | ||
179 | /// Gets or sets the action state of the designated Component. | ||
180 | /// </summary> | ||
181 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
182 | /// <exception cref="ArgumentException">an unknown Component was requested</exception> | ||
183 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
184 | /// <remarks><p> | ||
185 | /// Win32 MSI APIs: | ||
186 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>, | ||
187 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetcomponentstate.asp">MsiSetComponentState</a> | ||
188 | /// </p></remarks> | ||
189 | public InstallState RequestState | ||
190 | { | ||
191 | get | ||
192 | { | ||
193 | int installedState, actionState; | ||
194 | uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState); | ||
195 | if (ret != 0) | ||
196 | { | ||
197 | if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) | ||
198 | { | ||
199 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
200 | } | ||
201 | else | ||
202 | { | ||
203 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
204 | } | ||
205 | } | ||
206 | return (InstallState) actionState; | ||
207 | } | ||
208 | |||
209 | set | ||
210 | { | ||
211 | uint ret = RemotableNativeMethods.MsiSetComponentState((int) this.session.Handle, this.name, (int) value); | ||
212 | if (ret != 0) | ||
213 | { | ||
214 | if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) | ||
215 | { | ||
216 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
217 | } | ||
218 | else | ||
219 | { | ||
220 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /// <summary> | ||
227 | /// Gets disk space per drive required to install a component. | ||
228 | /// </summary> | ||
229 | /// <param name="installState">Requested component state</param> | ||
230 | /// <returns>A list of InstallCost structures, specifying the cost for each drive for the component</returns> | ||
231 | /// <remarks><p> | ||
232 | /// Win32 MSI API: | ||
233 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a> | ||
234 | /// </p></remarks> | ||
235 | public IList<InstallCost> GetCost(InstallState installState) | ||
236 | { | ||
237 | IList<InstallCost> costs = new List<InstallCost>(); | ||
238 | StringBuilder driveBuf = new StringBuilder(20); | ||
239 | for (uint i = 0; true; i++) | ||
240 | { | ||
241 | int cost, tempCost; | ||
242 | uint driveBufSize = (uint) driveBuf.Capacity; | ||
243 | uint ret = RemotableNativeMethods.MsiEnumComponentCosts( | ||
244 | (int) this.session.Handle, | ||
245 | this.name, | ||
246 | i, | ||
247 | (int) installState, | ||
248 | driveBuf, | ||
249 | ref driveBufSize, | ||
250 | out cost, | ||
251 | out tempCost); | ||
252 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
253 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
254 | { | ||
255 | driveBuf.Capacity = (int) ++driveBufSize; | ||
256 | ret = RemotableNativeMethods.MsiEnumComponentCosts( | ||
257 | (int) this.session.Handle, | ||
258 | this.name, | ||
259 | i, | ||
260 | (int) installState, | ||
261 | driveBuf, | ||
262 | ref driveBufSize, | ||
263 | out cost, | ||
264 | out tempCost); | ||
265 | } | ||
266 | |||
267 | if (ret != 0) | ||
268 | { | ||
269 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
270 | } | ||
271 | costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L)); | ||
272 | } | ||
273 | return costs; | ||
274 | } | ||
275 | } | ||
276 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs new file mode 100644 index 00000000..6d368899 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs | |||
@@ -0,0 +1,382 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Represents an instance of a registered component. | ||
12 | /// </summary> | ||
13 | public class ComponentInstallation : InstallationPart | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Gets the set of installed components for all products. | ||
17 | /// </summary> | ||
18 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
19 | /// <remarks><p> | ||
20 | /// Win32 MSI API: | ||
21 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponents.asp">MsiEnumComponents</a> | ||
22 | /// </p></remarks> | ||
23 | public static IEnumerable<ComponentInstallation> AllComponents | ||
24 | { | ||
25 | get | ||
26 | { | ||
27 | StringBuilder buf = new StringBuilder(40); | ||
28 | for (uint i = 0; true; i++) | ||
29 | { | ||
30 | uint ret = NativeMethods.MsiEnumComponents(i, buf); | ||
31 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
32 | if (ret != 0) | ||
33 | { | ||
34 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
35 | } | ||
36 | yield return new ComponentInstallation(buf.ToString()); | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets the set of installed components for products in the indicated context. | ||
43 | /// </summary> | ||
44 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
45 | /// <remarks><p> | ||
46 | /// Win32 MSI API: | ||
47 | /// <a href="http://msdn.microsoft.com/library/dd407947.aspx">MsiEnumComponentsEx</a> | ||
48 | /// </p></remarks> | ||
49 | public static IEnumerable<ComponentInstallation> Components(string szUserSid, UserContexts dwContext) | ||
50 | { | ||
51 | uint pcchSid = 32; | ||
52 | StringBuilder szSid = new StringBuilder((int)pcchSid); | ||
53 | StringBuilder buf = new StringBuilder(40); | ||
54 | UserContexts installedContext; | ||
55 | for (uint i = 0; true; i++) | ||
56 | { | ||
57 | uint ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid); | ||
58 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
59 | { | ||
60 | szSid.EnsureCapacity((int) ++pcchSid); | ||
61 | ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid); | ||
62 | } | ||
63 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
64 | if (ret != 0) | ||
65 | { | ||
66 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
67 | } | ||
68 | yield return new ComponentInstallation(buf.ToString(), szSid.ToString(), installedContext); | ||
69 | } | ||
70 | } | ||
71 | private static string GetProductCode(string component) | ||
72 | { | ||
73 | StringBuilder buf = new StringBuilder(40); | ||
74 | uint ret = NativeMethods.MsiGetProductCode(component, buf); | ||
75 | if (ret != 0) | ||
76 | { | ||
77 | return null; | ||
78 | } | ||
79 | |||
80 | return buf.ToString(); | ||
81 | } | ||
82 | |||
83 | private static string GetProductCode(string component, string szUserSid, UserContexts dwContext) | ||
84 | { | ||
85 | // TODO: We really need what would be MsiGetProductCodeEx, or at least a reasonable facimile thereof (something that restricts the search to the context defined by szUserSid & dwContext) | ||
86 | return GetProductCode(component); | ||
87 | } | ||
88 | |||
89 | /// <summary> | ||
90 | /// Creates a new ComponentInstallation, automatically detecting the | ||
91 | /// product that the component is a part of. | ||
92 | /// </summary> | ||
93 | /// <param name="componentCode">component GUID</param> | ||
94 | /// <remarks><p> | ||
95 | /// Win32 MSI API: | ||
96 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductcode.asp">MsiGetProductCode</a> | ||
97 | /// </p></remarks> | ||
98 | public ComponentInstallation(string componentCode) | ||
99 | : this(componentCode, ComponentInstallation.GetProductCode(componentCode)) | ||
100 | { | ||
101 | } | ||
102 | |||
103 | /// <summary> | ||
104 | /// Creates a new ComponentInstallation, automatically detecting the | ||
105 | /// product that the component is a part of. | ||
106 | /// </summary> | ||
107 | /// <param name="componentCode">component GUID</param> | ||
108 | /// <param name="szUserSid">context user SID</param> | ||
109 | /// <param name="dwContext">user contexts</param> | ||
110 | public ComponentInstallation(string componentCode, string szUserSid, UserContexts dwContext) | ||
111 | : this(componentCode, ComponentInstallation.GetProductCode(componentCode, szUserSid, dwContext), szUserSid, dwContext) | ||
112 | { | ||
113 | } | ||
114 | |||
115 | /// <summary> | ||
116 | /// Creates a new ComponentInstallation for a component installed by a | ||
117 | /// specific product. | ||
118 | /// </summary> | ||
119 | /// <param name="componentCode">component GUID</param> | ||
120 | /// <param name="productCode">ProductCode GUID</param> | ||
121 | public ComponentInstallation(string componentCode, string productCode) | ||
122 | : this(componentCode, productCode, null, UserContexts.None) | ||
123 | { | ||
124 | } | ||
125 | |||
126 | /// <summary> | ||
127 | /// Creates a new ComponentInstallation for a component installed by a | ||
128 | /// specific product. | ||
129 | /// </summary> | ||
130 | /// <param name="componentCode">component GUID</param> | ||
131 | /// <param name="productCode">ProductCode GUID</param> | ||
132 | /// <param name="szUserSid">context user SID</param> | ||
133 | /// <param name="dwContext">user contexts</param> | ||
134 | public ComponentInstallation(string componentCode, string productCode, string szUserSid, UserContexts dwContext) | ||
135 | : base(componentCode, productCode, szUserSid, dwContext) | ||
136 | { | ||
137 | if (string.IsNullOrEmpty(componentCode)) | ||
138 | { | ||
139 | throw new ArgumentNullException("componentCode"); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /// <summary> | ||
144 | /// Gets the component code (GUID) of the component. | ||
145 | /// </summary> | ||
146 | public string ComponentCode | ||
147 | { | ||
148 | get | ||
149 | { | ||
150 | return this.Id; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /// <summary> | ||
155 | /// Gets all client products of a specified component. | ||
156 | /// </summary> | ||
157 | /// <returns>enumeration over all client products of the component</returns> | ||
158 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
159 | /// <remarks><p> | ||
160 | /// Because clients are not ordered, any new component has an arbitrary index. | ||
161 | /// This means that the property may return clients in any order. | ||
162 | /// </p><p> | ||
163 | /// Win32 MSI API: | ||
164 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumclients.asp">MsiEnumClients</a>, | ||
165 | /// <a href="http://msdn.microsoft.com/library/dd407946.aspx">MsiEnumClientsEx</a> | ||
166 | /// </p></remarks> | ||
167 | public IEnumerable<ProductInstallation> ClientProducts | ||
168 | { | ||
169 | get | ||
170 | { | ||
171 | StringBuilder buf = new StringBuilder(40); | ||
172 | for (uint i = 0; true; i++) | ||
173 | { | ||
174 | uint chSid = 0; | ||
175 | UserContexts installedContext; | ||
176 | uint ret = this.Context == UserContexts.None ? | ||
177 | NativeMethods.MsiEnumClients(this.ComponentCode, i, buf) : | ||
178 | NativeMethods.MsiEnumClientsEx(this.ComponentCode, this.UserSid, this.Context, i, buf, out installedContext, null, ref chSid); | ||
179 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
180 | else if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) break; | ||
181 | if (ret != 0) | ||
182 | { | ||
183 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
184 | } | ||
185 | yield return new ProductInstallation(buf.ToString()); | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | |||
190 | /// <summary> | ||
191 | /// Gets the installed state of a component. | ||
192 | /// </summary> | ||
193 | /// <returns>the installed state of the component, or InstallState.Unknown | ||
194 | /// if this component is not part of a product</returns> | ||
195 | /// <remarks><p> | ||
196 | /// Win32 MSI API: | ||
197 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>, | ||
198 | /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a> | ||
199 | /// </p></remarks> | ||
200 | public override InstallState State | ||
201 | { | ||
202 | get | ||
203 | { | ||
204 | if (this.ProductCode != null) | ||
205 | { | ||
206 | uint bufSize = 0; | ||
207 | int installState = this.Context == UserContexts.None ? | ||
208 | NativeMethods.MsiGetComponentPath( | ||
209 | this.ProductCode, this.ComponentCode, null, ref bufSize) : | ||
210 | NativeMethods.MsiGetComponentPathEx( | ||
211 | this.ProductCode, this.ComponentCode, this.UserSid, this.Context, null, ref bufSize); | ||
212 | return (InstallState) installState; | ||
213 | } | ||
214 | else | ||
215 | { | ||
216 | return InstallState.Unknown; | ||
217 | } | ||
218 | } | ||
219 | } | ||
220 | |||
221 | /// <summary> | ||
222 | /// Gets the full path to an installed component. If the key path for the component is a | ||
223 | /// registry key then the registry key is returned. | ||
224 | /// </summary> | ||
225 | /// <returns>The file or registry keypath to the component, or null if the component is not available.</returns> | ||
226 | /// <exception cref="ArgumentException">An unknown product or component was specified</exception> | ||
227 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
228 | /// <remarks><p> | ||
229 | /// If the component is a registry key, the registry roots are represented numerically. | ||
230 | /// For example, a registry path of "HKEY_CURRENT_USER\SOFTWARE\Microsoft" would be returned | ||
231 | /// as "01:\SOFTWARE\Microsoft". The registry roots returned are defined as follows: | ||
232 | /// HKEY_CLASSES_ROOT=00, HKEY_CURRENT_USER=01, HKEY_LOCAL_MACHINE=02, HKEY_USERS=03, | ||
233 | /// HKEY_PERFORMANCE_DATA=04 | ||
234 | /// </p><p> | ||
235 | /// Win32 MSI APIs: | ||
236 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>, | ||
237 | /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>, | ||
238 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msilocatecomponent.asp">MsiLocateComponent</a> | ||
239 | /// </p></remarks> | ||
240 | public string Path | ||
241 | { | ||
242 | get | ||
243 | { | ||
244 | StringBuilder buf = new StringBuilder(256); | ||
245 | uint bufSize = (uint) buf.Capacity; | ||
246 | InstallState installState; | ||
247 | |||
248 | if (this.ProductCode != null) | ||
249 | { | ||
250 | installState = (this.Context == UserContexts.None) ? | ||
251 | (InstallState) NativeMethods.MsiGetComponentPath( | ||
252 | this.ProductCode, this.ComponentCode, buf, ref bufSize) : | ||
253 | (InstallState) NativeMethods.MsiGetComponentPathEx( | ||
254 | this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize); | ||
255 | if (installState == InstallState.MoreData) | ||
256 | { | ||
257 | buf.Capacity = (int) ++bufSize; | ||
258 | installState = (this.Context == UserContexts.None) ? | ||
259 | (InstallState) NativeMethods.MsiGetComponentPath( | ||
260 | this.ProductCode, this.ComponentCode, buf, ref bufSize) : | ||
261 | (InstallState) NativeMethods.MsiGetComponentPathEx( | ||
262 | this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize); | ||
263 | } | ||
264 | } | ||
265 | else | ||
266 | { | ||
267 | installState = (InstallState) NativeMethods.MsiLocateComponent( | ||
268 | this.ComponentCode, buf, ref bufSize); | ||
269 | if (installState == InstallState.MoreData) | ||
270 | { | ||
271 | buf.Capacity = (int) ++bufSize; | ||
272 | installState = (InstallState) NativeMethods.MsiLocateComponent( | ||
273 | this.ComponentCode, buf, ref bufSize); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | switch (installState) | ||
278 | { | ||
279 | case InstallState.Local: | ||
280 | case InstallState.Source: | ||
281 | case InstallState.SourceAbsent: | ||
282 | return buf.ToString(); | ||
283 | |||
284 | default: | ||
285 | return null; | ||
286 | } | ||
287 | } | ||
288 | } | ||
289 | |||
290 | /// <summary> | ||
291 | /// Gets the set of registered qualifiers for the component. | ||
292 | /// </summary> | ||
293 | /// <returns>Enumeration of the qulifiers for the component.</returns> | ||
294 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
295 | /// <remarks><p> | ||
296 | /// Because qualifiers are not ordered, any new qualifier has an arbitrary index, | ||
297 | /// meaning the function can return qualifiers in any order. | ||
298 | /// </p><p> | ||
299 | /// Win32 MSI API: | ||
300 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a> | ||
301 | /// </p></remarks> | ||
302 | public IEnumerable<ComponentInstallation.Qualifier> Qualifiers | ||
303 | { | ||
304 | get | ||
305 | { | ||
306 | StringBuilder qualBuf = new StringBuilder(255); | ||
307 | StringBuilder dataBuf = new StringBuilder(255); | ||
308 | for (uint i = 0; ; i++) | ||
309 | { | ||
310 | uint qualBufSize = (uint) qualBuf.Capacity; | ||
311 | uint dataBufSize = (uint) dataBuf.Capacity; | ||
312 | uint ret = NativeMethods.MsiEnumComponentQualifiers( | ||
313 | this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize); | ||
314 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
315 | { | ||
316 | qualBuf.Capacity = (int) ++qualBufSize; | ||
317 | dataBuf.Capacity = (int) ++dataBufSize; | ||
318 | ret = NativeMethods.MsiEnumComponentQualifiers( | ||
319 | this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize); | ||
320 | } | ||
321 | |||
322 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS || | ||
323 | ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) | ||
324 | { | ||
325 | break; | ||
326 | } | ||
327 | |||
328 | if (ret != 0) | ||
329 | { | ||
330 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
331 | } | ||
332 | |||
333 | yield return new ComponentInstallation.Qualifier( | ||
334 | qualBuf.ToString(), dataBuf.ToString()); | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Holds data about a component qualifier. | ||
341 | /// </summary> | ||
342 | /// <remarks><p> | ||
343 | /// Win32 MSI API: | ||
344 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a> | ||
345 | /// </p></remarks> | ||
346 | [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] | ||
347 | [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] | ||
348 | public struct Qualifier | ||
349 | { | ||
350 | private string qualifierCode; | ||
351 | private string data; | ||
352 | |||
353 | internal Qualifier(string qualifierCode, string data) | ||
354 | { | ||
355 | this.qualifierCode = qualifierCode; | ||
356 | this.data = data; | ||
357 | } | ||
358 | |||
359 | /// <summary> | ||
360 | /// Gets the qualifier code. | ||
361 | /// </summary> | ||
362 | public string QualifierCode | ||
363 | { | ||
364 | get | ||
365 | { | ||
366 | return this.qualifierCode; | ||
367 | } | ||
368 | } | ||
369 | |||
370 | /// <summary> | ||
371 | /// Gets the qualifier data. | ||
372 | /// </summary> | ||
373 | public string Data | ||
374 | { | ||
375 | get | ||
376 | { | ||
377 | return this.data; | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs new file mode 100644 index 00000000..d9bdb71b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Reflection; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Marks a method as a custom action entry point. | ||
10 | /// </summary> | ||
11 | /// <remarks><p> | ||
12 | /// A custom action method must be defined as public and static, | ||
13 | /// take a single <see cref="Session"/> object as a parameter, | ||
14 | /// and return an <see cref="ActionResult"/> enumeration value. | ||
15 | /// </p></remarks> | ||
16 | [Serializable, AttributeUsage(AttributeTargets.Method)] | ||
17 | public sealed class CustomActionAttribute : Attribute | ||
18 | { | ||
19 | /// <summary> | ||
20 | /// Name of the custom action entrypoint, or null if the same as the method name. | ||
21 | /// </summary> | ||
22 | private string name; | ||
23 | |||
24 | /// <summary> | ||
25 | /// Marks a method as a custom action entry point. | ||
26 | /// </summary> | ||
27 | public CustomActionAttribute() | ||
28 | : this(null) | ||
29 | { | ||
30 | } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Marks a method as a custom action entry point. | ||
34 | /// </summary> | ||
35 | /// <param name="name">Name of the function to be exported, | ||
36 | /// defaults to the name of this method</param> | ||
37 | public CustomActionAttribute(string name) | ||
38 | { | ||
39 | this.name = name; | ||
40 | } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Gets or sets the name of the custom action entrypoint. A null | ||
44 | /// value defaults to the name of the method. | ||
45 | /// </summary> | ||
46 | /// <value>name of the custom action entrypoint, or null if none was specified</value> | ||
47 | public string Name | ||
48 | { | ||
49 | get | ||
50 | { | ||
51 | return this.name; | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs new file mode 100644 index 00000000..d3fd7d1b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs | |||
@@ -0,0 +1,321 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Security; | ||
9 | using System.Reflection; | ||
10 | using System.Collections; | ||
11 | using System.Configuration; | ||
12 | using System.Runtime.InteropServices; | ||
13 | using System.Diagnostics.CodeAnalysis; | ||
14 | |||
15 | /// <summary> | ||
16 | /// Managed-code portion of the custom action proxy. | ||
17 | /// </summary> | ||
18 | internal static class CustomActionProxy | ||
19 | { | ||
20 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
21 | public static int InvokeCustomAction32(int sessionHandle, string entryPoint, | ||
22 | int remotingDelegatePtr) | ||
23 | { | ||
24 | return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr)); | ||
25 | } | ||
26 | |||
27 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
28 | public static int InvokeCustomAction64(int sessionHandle, string entryPoint, | ||
29 | long remotingDelegatePtr) | ||
30 | { | ||
31 | return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr)); | ||
32 | } | ||
33 | |||
34 | /// <summary> | ||
35 | /// Invokes a managed custom action method. | ||
36 | /// </summary> | ||
37 | /// <param name="sessionHandle">Integer handle to the installer session.</param> | ||
38 | /// <param name="entryPoint">Name of the custom action entrypoint. This must | ||
39 | /// either map to an entrypoint definition in the <c>customActions</c> | ||
40 | /// config section, or be an explicit entrypoint of the form: | ||
41 | /// "AssemblyName!Namespace.Class.Method"</param> | ||
42 | /// <param name="remotingDelegatePtr">Pointer to a delegate used to | ||
43 | /// make remote API calls, if this custom action is running out-of-proc.</param> | ||
44 | /// <returns>The value returned by the custom action method, | ||
45 | /// or ERROR_INSTALL_FAILURE if the custom action could not be invoked.</returns> | ||
46 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
47 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
48 | public static int InvokeCustomAction(int sessionHandle, string entryPoint, | ||
49 | IntPtr remotingDelegatePtr) | ||
50 | { | ||
51 | Session session = null; | ||
52 | string assemblyName, className, methodName; | ||
53 | MethodInfo method; | ||
54 | |||
55 | try | ||
56 | { | ||
57 | MsiRemoteInvoke remotingDelegate = (MsiRemoteInvoke) | ||
58 | Marshal.GetDelegateForFunctionPointer( | ||
59 | remotingDelegatePtr, typeof(MsiRemoteInvoke)); | ||
60 | RemotableNativeMethods.RemotingDelegate = remotingDelegate; | ||
61 | |||
62 | sessionHandle = RemotableNativeMethods.MakeRemoteHandle(sessionHandle); | ||
63 | session = new Session((IntPtr) sessionHandle, false); | ||
64 | if (String.IsNullOrEmpty(entryPoint)) | ||
65 | { | ||
66 | throw new ArgumentNullException("entryPoint"); | ||
67 | } | ||
68 | |||
69 | if (!CustomActionProxy.FindEntryPoint( | ||
70 | session, | ||
71 | entryPoint, | ||
72 | out assemblyName, | ||
73 | out className, | ||
74 | out methodName)) | ||
75 | { | ||
76 | return (int) ActionResult.Failure; | ||
77 | } | ||
78 | session.Log("Calling custom action {0}!{1}.{2}", assemblyName, className, methodName); | ||
79 | |||
80 | method = CustomActionProxy.GetCustomActionMethod( | ||
81 | session, | ||
82 | assemblyName, | ||
83 | className, | ||
84 | methodName); | ||
85 | if (method == null) | ||
86 | { | ||
87 | return (int) ActionResult.Failure; | ||
88 | } | ||
89 | } | ||
90 | catch (Exception ex) | ||
91 | { | ||
92 | if (session != null) | ||
93 | { | ||
94 | try | ||
95 | { | ||
96 | session.Log("Exception while loading custom action:"); | ||
97 | session.Log(ex.ToString()); | ||
98 | } | ||
99 | catch (Exception) { } | ||
100 | } | ||
101 | return (int) ActionResult.Failure; | ||
102 | } | ||
103 | |||
104 | try | ||
105 | { | ||
106 | // Set the current directory to the location of the extracted files. | ||
107 | Environment.CurrentDirectory = | ||
108 | AppDomain.CurrentDomain.BaseDirectory; | ||
109 | |||
110 | object[] args = new object[] { session }; | ||
111 | if (DebugBreakEnabled(new string[] { entryPoint, methodName })) | ||
112 | { | ||
113 | string message = String.Format( | ||
114 | "To debug your custom action, attach to process ID {0} (0x{0:x}) and click OK; otherwise, click Cancel to fail the custom action.", | ||
115 | System.Diagnostics.Process.GetCurrentProcess().Id | ||
116 | ); | ||
117 | |||
118 | MessageResult button = NativeMethods.MessageBox( | ||
119 | IntPtr.Zero, | ||
120 | message, | ||
121 | "Custom Action Breakpoint", | ||
122 | (int)MessageButtons.OKCancel | (int)MessageIcon.Asterisk | (int)(MessageBoxStyles.TopMost | MessageBoxStyles.ServiceNotification) | ||
123 | ); | ||
124 | |||
125 | if (MessageResult.Cancel == button) | ||
126 | { | ||
127 | return (int)ActionResult.UserExit; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | ActionResult result = (ActionResult) method.Invoke(null, args); | ||
132 | session.Close(); | ||
133 | return (int) result; | ||
134 | } | ||
135 | catch (InstallCanceledException) | ||
136 | { | ||
137 | return (int) ActionResult.UserExit; | ||
138 | } | ||
139 | catch (Exception ex) | ||
140 | { | ||
141 | session.Log("Exception thrown by custom action:"); | ||
142 | session.Log(ex.ToString()); | ||
143 | return (int) ActionResult.Failure; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | /// <summary> | ||
148 | /// Checks the "MMsiBreak" environment variable for any matching custom action names. | ||
149 | /// </summary> | ||
150 | /// <param name="names">List of names to search for in the environment | ||
151 | /// variable string.</param> | ||
152 | /// <returns>True if a match was found, else false.</returns> | ||
153 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
154 | internal static bool DebugBreakEnabled(string[] names) | ||
155 | { | ||
156 | string mmsibreak = Environment.GetEnvironmentVariable("MMsiBreak"); | ||
157 | if (mmsibreak != null) | ||
158 | { | ||
159 | foreach (string breakName in mmsibreak.Split(',', ';')) | ||
160 | { | ||
161 | foreach (string name in names) | ||
162 | { | ||
163 | if (breakName == name) | ||
164 | { | ||
165 | return true; | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | return false; | ||
171 | } | ||
172 | |||
173 | /// <summary> | ||
174 | /// Locates and parses an entrypoint mapping in CustomAction.config. | ||
175 | /// </summary> | ||
176 | /// <param name="session">Installer session handle, just used for logging.</param> | ||
177 | /// <param name="entryPoint">Custom action entrypoint name: the key value | ||
178 | /// in an item in the <c>customActions</c> section of the config file.</param> | ||
179 | /// <param name="assemblyName">Returned display name of the assembly from | ||
180 | /// the entrypoint mapping.</param> | ||
181 | /// <param name="className">Returned class name of the entrypoint mapping.</param> | ||
182 | /// <param name="methodName">Returned method name of the entrypoint mapping.</param> | ||
183 | /// <returns>True if the entrypoint was found, false if not or if some error | ||
184 | /// occurred.</returns> | ||
185 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
186 | private static bool FindEntryPoint( | ||
187 | Session session, | ||
188 | string entryPoint, | ||
189 | out string assemblyName, | ||
190 | out string className, | ||
191 | out string methodName) | ||
192 | { | ||
193 | assemblyName = null; | ||
194 | className = null; | ||
195 | methodName = null; | ||
196 | |||
197 | string fullEntryPoint; | ||
198 | if (entryPoint.IndexOf('!') > 0) | ||
199 | { | ||
200 | fullEntryPoint = entryPoint; | ||
201 | } | ||
202 | else | ||
203 | { | ||
204 | #if NET20 | ||
205 | IDictionary config; | ||
206 | try | ||
207 | { | ||
208 | config = (IDictionary) ConfigurationManager.GetSection("customActions"); | ||
209 | } | ||
210 | catch (ConfigurationException cex) | ||
211 | { | ||
212 | session.Log("Error: missing or invalid customActions config section."); | ||
213 | session.Log(cex.ToString()); | ||
214 | return false; | ||
215 | } | ||
216 | fullEntryPoint = (string) config[entryPoint]; | ||
217 | if (fullEntryPoint == null) | ||
218 | { | ||
219 | session.Log( | ||
220 | "Error: custom action entry point '{0}' not found " + | ||
221 | "in customActions config section.", | ||
222 | entryPoint); | ||
223 | return false; | ||
224 | } | ||
225 | #else | ||
226 | throw new NotImplementedException(); | ||
227 | #endif | ||
228 | } | ||
229 | |||
230 | int assemblySplit = fullEntryPoint.IndexOf('!'); | ||
231 | int methodSplit = fullEntryPoint.LastIndexOf('.'); | ||
232 | if (assemblySplit < 0 || methodSplit < 0 || methodSplit < assemblySplit) | ||
233 | { | ||
234 | session.Log("Error: invalid custom action entry point:" + entryPoint); | ||
235 | return false; | ||
236 | } | ||
237 | |||
238 | assemblyName = fullEntryPoint.Substring(0, assemblySplit); | ||
239 | className = fullEntryPoint.Substring(assemblySplit + 1, methodSplit - assemblySplit - 1); | ||
240 | methodName = fullEntryPoint.Substring(methodSplit + 1); | ||
241 | return true; | ||
242 | } | ||
243 | |||
244 | /// <summary> | ||
245 | /// Uses reflection to load the assembly and class and find the method. | ||
246 | /// </summary> | ||
247 | /// <param name="session">Installer session handle, just used for logging.</param> | ||
248 | /// <param name="assemblyName">Display name of the assembly containing the | ||
249 | /// custom action method.</param> | ||
250 | /// <param name="className">Fully-qualified name of the class containing the | ||
251 | /// custom action method.</param> | ||
252 | /// <param name="methodName">Name of the custom action method.</param> | ||
253 | /// <returns>The method, or null if not found.</returns> | ||
254 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
255 | private static MethodInfo GetCustomActionMethod( | ||
256 | Session session, | ||
257 | string assemblyName, | ||
258 | string className, | ||
259 | string methodName) | ||
260 | { | ||
261 | Assembly customActionAssembly; | ||
262 | Type customActionClass = null; | ||
263 | Exception caughtEx = null; | ||
264 | try | ||
265 | { | ||
266 | customActionAssembly = AppDomain.CurrentDomain.Load(assemblyName); | ||
267 | customActionClass = customActionAssembly.GetType(className, true, true); | ||
268 | } | ||
269 | catch (IOException ex) { caughtEx = ex; } | ||
270 | catch (BadImageFormatException ex) { caughtEx = ex; } | ||
271 | catch (TypeLoadException ex) { caughtEx = ex; } | ||
272 | catch (ReflectionTypeLoadException ex) { caughtEx = ex; } | ||
273 | catch (SecurityException ex) { caughtEx = ex; } | ||
274 | if (caughtEx != null) | ||
275 | { | ||
276 | session.Log("Error: could not load custom action class " + className + " from assembly: " + assemblyName); | ||
277 | session.Log(caughtEx.ToString()); | ||
278 | return null; | ||
279 | } | ||
280 | |||
281 | MethodInfo[] methods = customActionClass.GetMethods( | ||
282 | BindingFlags.Public | BindingFlags.Static); | ||
283 | foreach (MethodInfo method in methods) | ||
284 | { | ||
285 | if (method.Name == methodName && | ||
286 | CustomActionProxy.MethodHasCustomActionSignature(method)) | ||
287 | { | ||
288 | return method; | ||
289 | } | ||
290 | } | ||
291 | session.Log("Error: custom action method \"" + methodName + | ||
292 | "\" is missing or has the wrong signature."); | ||
293 | return null; | ||
294 | } | ||
295 | |||
296 | /// <summary> | ||
297 | /// Checks if a method has the right return and paramater types | ||
298 | /// for a custom action, and that it is marked by a CustomActionAttribute. | ||
299 | /// </summary> | ||
300 | /// <param name="method">Method to be checked.</param> | ||
301 | /// <returns>True if the method is a valid custom action, else false.</returns> | ||
302 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
303 | private static bool MethodHasCustomActionSignature(MethodInfo method) | ||
304 | { | ||
305 | if (method.ReturnType == typeof(ActionResult) && | ||
306 | method.GetParameters().Length == 1 && | ||
307 | method.GetParameters()[0].ParameterType == typeof(Session)) | ||
308 | { | ||
309 | object[] methodAttribs = method.GetCustomAttributes(false); | ||
310 | foreach (object attrib in methodAttribs) | ||
311 | { | ||
312 | if (attrib is CustomActionAttribute) | ||
313 | { | ||
314 | return true; | ||
315 | } | ||
316 | } | ||
317 | } | ||
318 | return false; | ||
319 | } | ||
320 | } | ||
321 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs new file mode 100644 index 00000000..09627f4b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs | |||
@@ -0,0 +1,933 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Accesses a Windows Installer database. | ||
13 | /// </summary> | ||
14 | /// <remarks><p> | ||
15 | /// The <see cref="Commit"/> method must be called before the Database is closed to write out all | ||
16 | /// persistent changes. If the Commit method is not called, the installer performs an implicit | ||
17 | /// rollback upon object destruction. | ||
18 | /// </p><p> | ||
19 | /// The client can use the following procedure for data access:<list type="number"> | ||
20 | /// <item><description>Obtain a Database object using one of the Database constructors.</description></item> | ||
21 | /// <item><description>Initiate a query using a SQL string by calling the <see cref="OpenView"/> | ||
22 | /// method of the Database.</description></item> | ||
23 | /// <item><description>Set query parameters in a <see cref="Record"/> and execute the database | ||
24 | /// query by calling the <see cref="View.Execute(Record)"/> method of the <see cref="View"/>. This | ||
25 | /// produces a result that can be fetched or updated.</description></item> | ||
26 | /// <item><description>Call the <see cref="View.Fetch"/> method of the View repeatedly to return | ||
27 | /// Records.</description></item> | ||
28 | /// <item><description>Update database rows of a Record object obtained by the Fetch method using | ||
29 | /// one of the <see cref="View.Modify"/> methods of the View.</description></item> | ||
30 | /// <item><description>Release the query and any unfetched records by calling the <see cref="InstallerHandle.Close"/> | ||
31 | /// method of the View.</description></item> | ||
32 | /// <item><description>Persist any database updates by calling the Commit method of the Database. | ||
33 | /// </description></item> | ||
34 | /// </list> | ||
35 | /// </p></remarks> | ||
36 | public partial class Database : InstallerHandle | ||
37 | { | ||
38 | private string filePath; | ||
39 | private DatabaseOpenMode openMode; | ||
40 | private SummaryInfo summaryInfo; | ||
41 | private TableCollection tables; | ||
42 | private IList<string> deleteOnClose; | ||
43 | |||
44 | /// <summary> | ||
45 | /// Opens an existing database in read-only mode. | ||
46 | /// </summary> | ||
47 | /// <param name="filePath">Path to the database file.</param> | ||
48 | /// <exception cref="InstallerException">the database could not be created/opened</exception> | ||
49 | /// <remarks><p> | ||
50 | /// Because this constructor initiates database access, it cannot be used with a | ||
51 | /// running installation. | ||
52 | /// </p><p> | ||
53 | /// The Database object should be <see cref="InstallerHandle.Close"/>d after use. | ||
54 | /// It is best that the handle be closed manually as soon as it is no longer | ||
55 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
56 | /// </p><p> | ||
57 | /// Win32 MSI API: | ||
58 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a> | ||
59 | /// </p></remarks> | ||
60 | public Database(string filePath) | ||
61 | : this(filePath, DatabaseOpenMode.ReadOnly) | ||
62 | { | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Opens an existing database with another database as output. | ||
67 | /// </summary> | ||
68 | /// <param name="filePath">Path to the database to be read.</param> | ||
69 | /// <param name="outputPath">Open mode for the database</param> | ||
70 | /// <returns>Database object representing the created or opened database</returns> | ||
71 | /// <exception cref="InstallerException">the database could not be created/opened</exception> | ||
72 | /// <remarks><p> | ||
73 | /// When a database is opened as the output of another database, the summary information stream | ||
74 | /// of the output database is actually a read-only mirror of the original database and thus cannot | ||
75 | /// be changed. Additionally, it is not persisted with the database. To create or modify the | ||
76 | /// summary information for the output database it must be closed and re-opened. | ||
77 | /// </p><p> | ||
78 | /// The Database object should be <see cref="InstallerHandle.Close"/>d after use. | ||
79 | /// It is best that the handle be closed manually as soon as it is no longer | ||
80 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
81 | /// </p><p> | ||
82 | /// The database is opened in <see cref="DatabaseOpenMode.CreateDirect" /> mode, and will be | ||
83 | /// automatically commited when it is closed. | ||
84 | /// </p><p> | ||
85 | /// Win32 MSI API: | ||
86 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a> | ||
87 | /// </p></remarks> | ||
88 | public Database(string filePath, string outputPath) | ||
89 | : this((IntPtr) Database.Open(filePath, outputPath), true, outputPath, DatabaseOpenMode.CreateDirect) | ||
90 | { | ||
91 | } | ||
92 | |||
93 | /// <summary> | ||
94 | /// Opens an existing database or creates a new one. | ||
95 | /// </summary> | ||
96 | /// <param name="filePath">Path to the database file. If an empty string | ||
97 | /// is supplied, a temporary database is created that is not persisted.</param> | ||
98 | /// <param name="mode">Open mode for the database</param> | ||
99 | /// <exception cref="InstallerException">the database could not be created/opened</exception> | ||
100 | /// <remarks><p> | ||
101 | /// Because this constructor initiates database access, it cannot be used with a | ||
102 | /// running installation. | ||
103 | /// </p><p> | ||
104 | /// The database object should be <see cref="InstallerHandle.Close"/>d after use. | ||
105 | /// The finalizer will close the handle if it is still open, however due to the nondeterministic | ||
106 | /// nature of finalization it is best that the handle be closed manually as soon as it is no | ||
107 | /// longer needed, as leaving lots of unused handles open can degrade performance. | ||
108 | /// </p><p> | ||
109 | /// A database opened in <see cref="DatabaseOpenMode.CreateDirect" /> or | ||
110 | /// <see cref="DatabaseOpenMode.Direct" /> mode will be automatically commited when it is | ||
111 | /// closed. However a database opened in <see cref="DatabaseOpenMode.Create" /> or | ||
112 | /// <see cref="DatabaseOpenMode.Transact" /> mode must have the <see cref="Commit" /> method | ||
113 | /// called before it is closed, otherwise no changes will be persisted. | ||
114 | /// </p><p> | ||
115 | /// Win32 MSI API: | ||
116 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a> | ||
117 | /// </p></remarks> | ||
118 | public Database(string filePath, DatabaseOpenMode mode) | ||
119 | : this((IntPtr) Database.Open(filePath, mode), true, filePath, mode) | ||
120 | { | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Creates a new database from an MSI handle. | ||
125 | /// </summary> | ||
126 | /// <param name="handle">Native MSI database handle.</param> | ||
127 | /// <param name="ownsHandle">True if the handle should be closed | ||
128 | /// when the database object is disposed</param> | ||
129 | /// <param name="filePath">Path of the database file, if known</param> | ||
130 | /// <param name="openMode">Mode the handle was originally opened in</param> | ||
131 | protected internal Database( | ||
132 | IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode) | ||
133 | : base(handle, ownsHandle) | ||
134 | { | ||
135 | this.filePath = filePath; | ||
136 | this.openMode = openMode; | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Gets the file path the Database was originally opened from, or null if not known. | ||
141 | /// </summary> | ||
142 | public String FilePath | ||
143 | { | ||
144 | get | ||
145 | { | ||
146 | return this.filePath; | ||
147 | } | ||
148 | } | ||
149 | |||
150 | /// <summary> | ||
151 | /// Gets the open mode for the database. | ||
152 | /// </summary> | ||
153 | public DatabaseOpenMode OpenMode | ||
154 | { | ||
155 | get | ||
156 | { | ||
157 | return this.openMode; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | /// <summary> | ||
162 | /// Gets a boolean value indicating whether this database was opened in read-only mode. | ||
163 | /// </summary> | ||
164 | /// <remarks><p> | ||
165 | /// Win32 MSI API: | ||
166 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetdatabasestate.asp">MsiGetDatabaseState</a> | ||
167 | /// </p></remarks> | ||
168 | public bool IsReadOnly | ||
169 | { | ||
170 | get | ||
171 | { | ||
172 | if (RemotableNativeMethods.RemotingEnabled) | ||
173 | { | ||
174 | return true; | ||
175 | } | ||
176 | |||
177 | int state = NativeMethods.MsiGetDatabaseState((int) this.Handle); | ||
178 | return state != 1; | ||
179 | } | ||
180 | } | ||
181 | |||
182 | /// <summary> | ||
183 | /// Gets the collection of tables in the Database. | ||
184 | /// </summary> | ||
185 | public TableCollection Tables | ||
186 | { | ||
187 | get | ||
188 | { | ||
189 | if (this.tables == null) | ||
190 | { | ||
191 | this.tables = new TableCollection(this); | ||
192 | } | ||
193 | return this.tables; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | /// <summary> | ||
198 | /// Gets or sets the code page of the Database. | ||
199 | /// </summary> | ||
200 | /// <exception cref="IOException">error exporting/importing the codepage data</exception> | ||
201 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
202 | /// <remarks><p> | ||
203 | /// Getting or setting the code page is a slow operation because it involves an export or import | ||
204 | /// of the codepage data to/from a temporary file. | ||
205 | /// </p></remarks> | ||
206 | public int CodePage | ||
207 | { | ||
208 | get | ||
209 | { | ||
210 | string tempFile = Path.GetTempFileName(); | ||
211 | StreamReader reader = null; | ||
212 | try | ||
213 | { | ||
214 | this.Export("_ForceCodepage", tempFile); | ||
215 | reader = File.OpenText(tempFile); | ||
216 | reader.ReadLine(); // Skip column name record. | ||
217 | reader.ReadLine(); // Skip column defn record. | ||
218 | string codePageLine = reader.ReadLine(); | ||
219 | return Int32.Parse(codePageLine.Split('\t')[0], CultureInfo.InvariantCulture.NumberFormat); | ||
220 | } | ||
221 | finally | ||
222 | { | ||
223 | if (reader != null) reader.Close(); | ||
224 | File.Delete(tempFile); | ||
225 | } | ||
226 | } | ||
227 | |||
228 | set | ||
229 | { | ||
230 | string tempFile = Path.GetTempFileName(); | ||
231 | StreamWriter writer = null; | ||
232 | try | ||
233 | { | ||
234 | writer = File.AppendText(tempFile); | ||
235 | writer.WriteLine(""); | ||
236 | writer.WriteLine(""); | ||
237 | writer.WriteLine("{0}\t_ForceCodepage", value); | ||
238 | writer.Close(); | ||
239 | writer = null; | ||
240 | this.Import(tempFile); | ||
241 | } | ||
242 | finally | ||
243 | { | ||
244 | if (writer != null) writer.Close(); | ||
245 | File.Delete(tempFile); | ||
246 | } | ||
247 | } | ||
248 | } | ||
249 | |||
250 | /// <summary> | ||
251 | /// Gets the SummaryInfo object for this database that can be used to examine and modify properties | ||
252 | /// to the summary information stream. | ||
253 | /// </summary> | ||
254 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
255 | /// <remarks><p> | ||
256 | /// The object returned from this property does not need to be explicitly persisted or closed. | ||
257 | /// Any modifications will be automatically saved when the database is committed. | ||
258 | /// </p><p> | ||
259 | /// Win32 MSI API: | ||
260 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a> | ||
261 | /// </p></remarks> | ||
262 | public SummaryInfo SummaryInfo | ||
263 | { | ||
264 | get | ||
265 | { | ||
266 | if (this.summaryInfo == null || this.summaryInfo.IsClosed) | ||
267 | { | ||
268 | lock (this.Sync) | ||
269 | { | ||
270 | if (this.summaryInfo == null || this.summaryInfo.IsClosed) | ||
271 | { | ||
272 | int summaryInfoHandle; | ||
273 | int maxProperties = this.IsReadOnly ? 0 : SummaryInfo.MAX_PROPERTIES; | ||
274 | uint ret = RemotableNativeMethods.MsiGetSummaryInformation((int) this.Handle, null, (uint) maxProperties, out summaryInfoHandle); | ||
275 | if (ret != 0) | ||
276 | { | ||
277 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
278 | } | ||
279 | this.summaryInfo = new SummaryInfo((IntPtr) summaryInfoHandle, true); | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | return this.summaryInfo; | ||
284 | } | ||
285 | } | ||
286 | |||
287 | /// <summary> | ||
288 | /// Creates a new Database object from an integer database handle. | ||
289 | /// </summary> | ||
290 | /// <remarks><p> | ||
291 | /// This method is only provided for interop purposes. A Database object | ||
292 | /// should normally be obtained from <see cref="Session.Database"/> or | ||
293 | /// a public Database constructor. | ||
294 | /// </p></remarks> | ||
295 | /// <param name="handle">Integer database handle</param> | ||
296 | /// <param name="ownsHandle">true to close the handle when this object is disposed</param> | ||
297 | public static Database FromHandle(IntPtr handle, bool ownsHandle) | ||
298 | { | ||
299 | return new Database( | ||
300 | handle, | ||
301 | ownsHandle, | ||
302 | null, | ||
303 | NativeMethods.MsiGetDatabaseState((int) handle) == 1 ? DatabaseOpenMode.Direct : DatabaseOpenMode.ReadOnly); | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Schedules a file or directory for deletion after the database handle is closed. | ||
308 | /// </summary> | ||
309 | /// <param name="path">File or directory path to be deleted. All files and subdirectories | ||
310 | /// under a directory are deleted.</param> | ||
311 | /// <remarks><p> | ||
312 | /// Once an item is scheduled, it cannot be unscheduled. | ||
313 | /// </p><p> | ||
314 | /// The items cannot be deleted if the Database object is auto-disposed by the | ||
315 | /// garbage collector; the handle must be explicitly closed. | ||
316 | /// </p><p> | ||
317 | /// Files which are read-only or otherwise locked cannot be deleted, | ||
318 | /// but they will not cause an exception to be thrown. | ||
319 | /// </p></remarks> | ||
320 | public void DeleteOnClose(string path) | ||
321 | { | ||
322 | if (this.deleteOnClose == null) | ||
323 | { | ||
324 | this.deleteOnClose = new List<string>(); | ||
325 | } | ||
326 | this.deleteOnClose.Add(path); | ||
327 | } | ||
328 | |||
329 | /// <summary> | ||
330 | /// Merges another database with this database. | ||
331 | /// </summary> | ||
332 | /// <param name="otherDatabase">The database to be merged into this database</param> | ||
333 | /// <param name="errorTable">Optional name of table to contain the names of the tables containing | ||
334 | /// merge conflicts, the number of conflicting rows within the table, and a reference to the table | ||
335 | /// with the merge conflict.</param> | ||
336 | /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception> | ||
337 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
338 | /// <remarks><p> | ||
339 | /// Merge does not copy over embedded cabinet files or embedded transforms from the | ||
340 | /// reference database into the target database. Embedded data streams that are listed in the | ||
341 | /// Binary table or Icon table are copied from the reference database to the target database. | ||
342 | /// Storage embedded in the reference database are not copied to the target database. | ||
343 | /// </p><p> | ||
344 | /// The Merge method merges the data of two databases. These databases must have the same | ||
345 | /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists | ||
346 | /// if the data in any row in the first database differs from the data in the corresponding row | ||
347 | /// of the second database. Corresponding rows are in the same table of both databases and have | ||
348 | /// the same primary key in both databases. The tables of non-conflicting databases must have | ||
349 | /// the same number of primary keys, same number of columns, same column types, same column names, | ||
350 | /// and the same data in rows with identical primary keys. Temporary columns however don't matter | ||
351 | /// in the column count and corresponding tables can have a different number of temporary columns | ||
352 | /// without creating conflict as long as the persistent columns match. | ||
353 | /// </p><p> | ||
354 | /// If the number, type, or name of columns in corresponding tables are different, the | ||
355 | /// schema of the two databases are incompatible and the installer will stop processing tables | ||
356 | /// and the merge fails. The installer checks that the two databases have the same schema before | ||
357 | /// checking for row merge conflicts. If the schemas are incompatible, the databases have be | ||
358 | /// modified. | ||
359 | /// </p><p> | ||
360 | /// If the data in particular rows differ, this is a row merge conflict, the merge fails | ||
361 | /// and creates a new table with the specified name. The first column of this table is the name | ||
362 | /// of the table having the conflict. The second column gives the number of rows in the table | ||
363 | /// having the conflict. | ||
364 | /// </p><p> | ||
365 | /// Win32 MSI API: | ||
366 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a> | ||
367 | /// </p></remarks> | ||
368 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
369 | public void Merge(Database otherDatabase, string errorTable) | ||
370 | { | ||
371 | if (otherDatabase == null) | ||
372 | { | ||
373 | throw new ArgumentNullException("otherDatabase"); | ||
374 | } | ||
375 | |||
376 | uint ret = NativeMethods.MsiDatabaseMerge((int) this.Handle, (int) otherDatabase.Handle, errorTable); | ||
377 | if (ret != 0) | ||
378 | { | ||
379 | if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED) | ||
380 | { | ||
381 | throw new MergeException(this, errorTable); | ||
382 | } | ||
383 | else if (ret == (uint) NativeMethods.Error.DATATYPE_MISMATCH) | ||
384 | { | ||
385 | throw new MergeException("Schema difference between the two databases."); | ||
386 | } | ||
387 | else | ||
388 | { | ||
389 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
390 | } | ||
391 | } | ||
392 | } | ||
393 | |||
394 | /// <summary> | ||
395 | /// Merges another database with this database. | ||
396 | /// </summary> | ||
397 | /// <param name="otherDatabase">The database to be merged into this database</param> | ||
398 | /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception> | ||
399 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
400 | /// <remarks><p> | ||
401 | /// MsiDatabaseMerge does not copy over embedded cabinet files or embedded transforms from | ||
402 | /// the reference database into the target database. Embedded data streams that are listed in | ||
403 | /// the Binary table or Icon table are copied from the reference database to the target database. | ||
404 | /// Storage embedded in the reference database are not copied to the target database. | ||
405 | /// </p><p> | ||
406 | /// The Merge method merges the data of two databases. These databases must have the same | ||
407 | /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists | ||
408 | /// if the data in any row in the first database differs from the data in the corresponding row | ||
409 | /// of the second database. Corresponding rows are in the same table of both databases and have | ||
410 | /// the same primary key in both databases. The tables of non-conflicting databases must have | ||
411 | /// the same number of primary keys, same number of columns, same column types, same column names, | ||
412 | /// and the same data in rows with identical primary keys. Temporary columns however don't matter | ||
413 | /// in the column count and corresponding tables can have a different number of temporary columns | ||
414 | /// without creating conflict as long as the persistent columns match. | ||
415 | /// </p><p> | ||
416 | /// If the number, type, or name of columns in corresponding tables are different, the | ||
417 | /// schema of the two databases are incompatible and the installer will stop processing tables | ||
418 | /// and the merge fails. The installer checks that the two databases have the same schema before | ||
419 | /// checking for row merge conflicts. If the schemas are incompatible, the databases have be | ||
420 | /// modified. | ||
421 | /// </p><p> | ||
422 | /// Win32 MSI API: | ||
423 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a> | ||
424 | /// </p></remarks> | ||
425 | public void Merge(Database otherDatabase) { this.Merge(otherDatabase, null); } | ||
426 | |||
427 | /// <summary> | ||
428 | /// Checks whether a table exists and is persistent in the database. | ||
429 | /// </summary> | ||
430 | /// <param name="table">The table to the checked</param> | ||
431 | /// <returns>true if the table exists and is persistent in the database; false otherwise</returns> | ||
432 | /// <exception cref="ArgumentException">the table is unknown</exception> | ||
433 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
434 | /// <remarks><p> | ||
435 | /// To check whether a table exists regardless of persistence, | ||
436 | /// use <see cref="TableCollection.Contains"/>. | ||
437 | /// </p><p> | ||
438 | /// Win32 MSI API: | ||
439 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseistablepersistent.asp">MsiDatabaseIsTablePersistent</a> | ||
440 | /// </p></remarks> | ||
441 | public bool IsTablePersistent(string table) | ||
442 | { | ||
443 | if (String.IsNullOrEmpty(table)) | ||
444 | { | ||
445 | throw new ArgumentNullException("table"); | ||
446 | } | ||
447 | uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.Handle, table); | ||
448 | if (ret == 3) // MSICONDITION_ERROR | ||
449 | { | ||
450 | throw new InstallerException(); | ||
451 | } | ||
452 | return ret == 1; | ||
453 | } | ||
454 | |||
455 | /// <summary> | ||
456 | /// Checks whether a table contains a persistent column with a given name. | ||
457 | /// </summary> | ||
458 | /// <param name="table">The table to the checked</param> | ||
459 | /// <param name="column">The name of the column to be checked</param> | ||
460 | /// <returns>true if the column exists in the table; false if the column is temporary or does not exist.</returns> | ||
461 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
462 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
463 | /// <remarks><p> | ||
464 | /// To check whether a column exists regardless of persistence, | ||
465 | /// use <see cref="ColumnCollection.Contains"/>. | ||
466 | /// </p></remarks> | ||
467 | public bool IsColumnPersistent(string table, string column) | ||
468 | { | ||
469 | if (String.IsNullOrEmpty(table)) | ||
470 | { | ||
471 | throw new ArgumentNullException("table"); | ||
472 | } | ||
473 | if (String.IsNullOrEmpty(column)) | ||
474 | { | ||
475 | throw new ArgumentNullException("column"); | ||
476 | } | ||
477 | using (View view = this.OpenView( | ||
478 | "SELECT `Number` FROM `_Columns` WHERE `Table` = '{0}' AND `Name` = '{1}'", table, column)) | ||
479 | { | ||
480 | view.Execute(); | ||
481 | using (Record rec = view.Fetch()) | ||
482 | { | ||
483 | return (rec != null); | ||
484 | } | ||
485 | } | ||
486 | } | ||
487 | |||
488 | /// <summary> | ||
489 | /// Gets the count of all rows in the table. | ||
490 | /// </summary> | ||
491 | /// <param name="table">Name of the table whose rows are to be counted</param> | ||
492 | /// <returns>The count of all rows in the table</returns> | ||
493 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
494 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
495 | public int CountRows(string table) | ||
496 | { | ||
497 | return this.CountRows(table, null); | ||
498 | } | ||
499 | |||
500 | /// <summary> | ||
501 | /// Gets the count of all rows in the table that satisfy a given condition. | ||
502 | /// </summary> | ||
503 | /// <param name="table">Name of the table whose rows are to be counted</param> | ||
504 | /// <param name="where">Conditional expression, such as could be placed on the end of a SQL WHERE clause</param> | ||
505 | /// <returns>The count of all rows in the table satisfying the condition</returns> | ||
506 | /// <exception cref="BadQuerySyntaxException">the SQL WHERE syntax is invalid</exception> | ||
507 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
508 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
509 | public int CountRows(string table, string where) | ||
510 | { | ||
511 | if (String.IsNullOrEmpty(table)) | ||
512 | { | ||
513 | throw new ArgumentNullException("table"); | ||
514 | } | ||
515 | |||
516 | // to support temporary tables like _Streams, run the query even if the table isn't persistent | ||
517 | TableInfo tableInfo = this.Tables[table]; | ||
518 | string primaryKeys = tableInfo == null ? "*" : String.Concat("`", tableInfo.PrimaryKeys[0], "`"); | ||
519 | int count; | ||
520 | |||
521 | try | ||
522 | { | ||
523 | using (View view = this.OpenView( | ||
524 | "SELECT {0} FROM `{1}`{2}", | ||
525 | primaryKeys, | ||
526 | table, | ||
527 | (where != null && where.Length != 0 ? " WHERE " + where : ""))) | ||
528 | { | ||
529 | view.Execute(); | ||
530 | for (count = 0; ; count++) | ||
531 | { | ||
532 | // Avoid creating unnecessary Record objects by not calling View.Fetch(). | ||
533 | int recordHandle; | ||
534 | uint ret = RemotableNativeMethods.MsiViewFetch((int)view.Handle, out recordHandle); | ||
535 | if (ret == (uint)NativeMethods.Error.NO_MORE_ITEMS) | ||
536 | { | ||
537 | break; | ||
538 | } | ||
539 | |||
540 | if (ret != 0) | ||
541 | { | ||
542 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
543 | } | ||
544 | |||
545 | RemotableNativeMethods.MsiCloseHandle(recordHandle); | ||
546 | } | ||
547 | } | ||
548 | } | ||
549 | catch (BadQuerySyntaxException) | ||
550 | { | ||
551 | // table was missing | ||
552 | count = 0; | ||
553 | } | ||
554 | |||
555 | return count; | ||
556 | } | ||
557 | |||
558 | /// <summary> | ||
559 | /// Finalizes the persistent form of the database. All persistent data is written | ||
560 | /// to the writeable database, and no temporary columns or rows are written. | ||
561 | /// </summary> | ||
562 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
563 | /// <remarks><p> | ||
564 | /// For a database open in <see cref="DatabaseOpenMode.ReadOnly"/> mode, this method has no effect. | ||
565 | /// </p><p> | ||
566 | /// For a database open in <see cref="DatabaseOpenMode.CreateDirect" /> or <see cref="DatabaseOpenMode.Direct" /> | ||
567 | /// mode, it is not necessary to call this method because the database will be automatically committed | ||
568 | /// when it is closed. However this method may be called at any time to persist the current state of tables | ||
569 | /// loaded into memory. | ||
570 | /// </p><p> | ||
571 | /// For a database open in <see cref="DatabaseOpenMode.Create" /> or <see cref="DatabaseOpenMode.Transact" /> | ||
572 | /// mode, no changes will be persisted until this method is called. If the database object is closed without | ||
573 | /// calling this method, the database file remains unmodified. | ||
574 | /// </p><p> | ||
575 | /// Win32 MSI API: | ||
576 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasecommit.asp">MsiDatabaseCommit</a> | ||
577 | /// </p></remarks> | ||
578 | public void Commit() | ||
579 | { | ||
580 | if (this.summaryInfo != null && !this.summaryInfo.IsClosed) | ||
581 | { | ||
582 | this.summaryInfo.Persist(); | ||
583 | this.summaryInfo.Close(); | ||
584 | this.summaryInfo = null; | ||
585 | } | ||
586 | uint ret = NativeMethods.MsiDatabaseCommit((int) this.Handle); | ||
587 | if (ret != 0) | ||
588 | { | ||
589 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | /// <summary> | ||
594 | /// Copies the structure and data from a specified table to a text archive file. | ||
595 | /// </summary> | ||
596 | /// <param name="table">Name of the table to be exported</param> | ||
597 | /// <param name="exportFilePath">Path to the file to be created</param> | ||
598 | /// <exception cref="FileNotFoundException">the file path is invalid</exception> | ||
599 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
600 | /// <remarks><p> | ||
601 | /// Win32 MSI API: | ||
602 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a> | ||
603 | /// </p></remarks> | ||
604 | public void Export(string table, string exportFilePath) | ||
605 | { | ||
606 | if (table == null) | ||
607 | { | ||
608 | throw new ArgumentNullException("table"); | ||
609 | } | ||
610 | |||
611 | FileInfo file = new FileInfo(exportFilePath); | ||
612 | uint ret = NativeMethods.MsiDatabaseExport((int) this.Handle, table, file.DirectoryName, file.Name); | ||
613 | if (ret != 0) | ||
614 | { | ||
615 | if (ret == (uint) NativeMethods.Error.BAD_PATHNAME) | ||
616 | { | ||
617 | throw new FileNotFoundException(null, exportFilePath); | ||
618 | } | ||
619 | else | ||
620 | { | ||
621 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
622 | } | ||
623 | } | ||
624 | } | ||
625 | |||
626 | /// <summary> | ||
627 | /// Imports a database table from a text archive file, dropping any existing table. | ||
628 | /// </summary> | ||
629 | /// <param name="importFilePath">Path to the file to be imported. | ||
630 | /// The table name is specified within the file.</param> | ||
631 | /// <exception cref="FileNotFoundException">the file path is invalid</exception> | ||
632 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
633 | /// <remarks><p> | ||
634 | /// Win32 MSI API: | ||
635 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a> | ||
636 | /// </p></remarks> | ||
637 | public void Import(string importFilePath) | ||
638 | { | ||
639 | if (String.IsNullOrEmpty(importFilePath)) | ||
640 | { | ||
641 | throw new ArgumentNullException("importFilePath"); | ||
642 | } | ||
643 | |||
644 | FileInfo file = new FileInfo(importFilePath); | ||
645 | uint ret = NativeMethods.MsiDatabaseImport((int) this.Handle, file.DirectoryName, file.Name); | ||
646 | if (ret != 0) | ||
647 | { | ||
648 | if (ret == (uint) NativeMethods.Error.BAD_PATHNAME) | ||
649 | { | ||
650 | throw new FileNotFoundException(null, importFilePath); | ||
651 | } | ||
652 | else | ||
653 | { | ||
654 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
655 | } | ||
656 | } | ||
657 | } | ||
658 | |||
659 | /// <summary> | ||
660 | /// Exports all database tables, streams, and summary information to archive files. | ||
661 | /// </summary> | ||
662 | /// <param name="directoryPath">Path to the directory where archive files will be created</param> | ||
663 | /// <exception cref="FileNotFoundException">the directory path is invalid</exception> | ||
664 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
665 | /// <remarks><p> | ||
666 | /// The directory will be created if it does not already exist. | ||
667 | /// </p><p> | ||
668 | /// Win32 MSI API: | ||
669 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a> | ||
670 | /// </p></remarks> | ||
671 | public void ExportAll(string directoryPath) | ||
672 | { | ||
673 | if (String.IsNullOrEmpty(directoryPath)) | ||
674 | { | ||
675 | throw new ArgumentNullException("directoryPath"); | ||
676 | } | ||
677 | |||
678 | if (!Directory.Exists(directoryPath)) | ||
679 | { | ||
680 | Directory.CreateDirectory(directoryPath); | ||
681 | } | ||
682 | |||
683 | this.Export("_SummaryInformation", Path.Combine(directoryPath, "_SummaryInformation.idt")); | ||
684 | |||
685 | using (View view = this.OpenView("SELECT `Name` FROM `_Tables`")) | ||
686 | { | ||
687 | view.Execute(); | ||
688 | |||
689 | foreach (Record rec in view) using (rec) | ||
690 | { | ||
691 | string table = (string) rec[1]; | ||
692 | |||
693 | this.Export(table, Path.Combine(directoryPath, table + ".idt")); | ||
694 | } | ||
695 | } | ||
696 | |||
697 | if (!Directory.Exists(Path.Combine(directoryPath, "_Streams"))) | ||
698 | { | ||
699 | Directory.CreateDirectory(Path.Combine(directoryPath, "_Streams")); | ||
700 | } | ||
701 | |||
702 | using (View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`")) | ||
703 | { | ||
704 | view.Execute(); | ||
705 | |||
706 | foreach (Record rec in view) using (rec) | ||
707 | { | ||
708 | string stream = (string) rec[1]; | ||
709 | if (stream.EndsWith("SummaryInformation", StringComparison.Ordinal)) continue; | ||
710 | |||
711 | int i = stream.IndexOf('.'); | ||
712 | if (i >= 0) | ||
713 | { | ||
714 | if (File.Exists(Path.Combine( | ||
715 | directoryPath, | ||
716 | Path.Combine(stream.Substring(0, i), stream.Substring(i + 1) + ".ibd")))) | ||
717 | { | ||
718 | continue; | ||
719 | } | ||
720 | } | ||
721 | rec.GetStream(2, Path.Combine(directoryPath, Path.Combine("_Streams", stream))); | ||
722 | } | ||
723 | } | ||
724 | } | ||
725 | |||
726 | /// <summary> | ||
727 | /// Imports all database tables, streams, and summary information from archive files. | ||
728 | /// </summary> | ||
729 | /// <param name="directoryPath">Path to the directory from which archive files will be imported</param> | ||
730 | /// <exception cref="FileNotFoundException">the directory path is invalid</exception> | ||
731 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
732 | /// <remarks><p> | ||
733 | /// Win32 MSI API: | ||
734 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a> | ||
735 | /// </p></remarks> | ||
736 | public void ImportAll(string directoryPath) | ||
737 | { | ||
738 | if (String.IsNullOrEmpty(directoryPath)) | ||
739 | { | ||
740 | throw new ArgumentNullException("directoryPath"); | ||
741 | } | ||
742 | |||
743 | if (File.Exists(Path.Combine(directoryPath, "_SummaryInformation.idt"))) | ||
744 | { | ||
745 | this.Import(Path.Combine(directoryPath, "_SummaryInformation.idt")); | ||
746 | } | ||
747 | |||
748 | string[] idtFiles = Directory.GetFiles(directoryPath, "*.idt"); | ||
749 | foreach (string file in idtFiles) | ||
750 | { | ||
751 | if (Path.GetFileName(file) != "_SummaryInformation.idt") | ||
752 | { | ||
753 | this.Import(file); | ||
754 | } | ||
755 | } | ||
756 | |||
757 | if (Directory.Exists(Path.Combine(directoryPath, "_Streams"))) | ||
758 | { | ||
759 | View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`"); | ||
760 | Record rec = null; | ||
761 | try | ||
762 | { | ||
763 | view.Execute(); | ||
764 | string[] streamFiles = Directory.GetFiles(Path.Combine(directoryPath, "_Streams")); | ||
765 | foreach (string file in streamFiles) | ||
766 | { | ||
767 | rec = this.CreateRecord(2); | ||
768 | rec[1] = Path.GetFileName(file); | ||
769 | rec.SetStream(2, file); | ||
770 | view.Insert(rec); | ||
771 | rec.Close(); | ||
772 | rec = null; | ||
773 | } | ||
774 | } | ||
775 | finally | ||
776 | { | ||
777 | if (rec != null) rec.Close(); | ||
778 | view.Close(); | ||
779 | } | ||
780 | } | ||
781 | } | ||
782 | |||
783 | /// <summary> | ||
784 | /// Creates a new record object with the requested number of fields. | ||
785 | /// </summary> | ||
786 | /// <param name="fieldCount">Required number of fields, which may be 0. | ||
787 | /// The maximum number of fields in a record is limited to 65535.</param> | ||
788 | /// <returns>A new record object that can be used with the database.</returns> | ||
789 | /// <remarks><p> | ||
790 | /// This method is equivalent to directly calling the <see cref="Record" /> | ||
791 | /// constructor in all cases outside of a custom action context. When in a | ||
792 | /// custom action session, this method allows creation of a record that can | ||
793 | /// work with a database other than the session database. | ||
794 | /// </p><p> | ||
795 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
796 | /// It is best that the handle be closed manually as soon as it is no longer | ||
797 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
798 | /// </p><p> | ||
799 | /// Win32 MSI API: | ||
800 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a> | ||
801 | /// </p></remarks> | ||
802 | public Record CreateRecord(int fieldCount) | ||
803 | { | ||
804 | int hRecord = RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, (int) this.Handle); | ||
805 | return new Record((IntPtr) hRecord, true, (View) null); | ||
806 | } | ||
807 | |||
808 | /// <summary> | ||
809 | /// Returns the file path of this database, or the handle value if a file path was not specified. | ||
810 | /// </summary> | ||
811 | public override string ToString() | ||
812 | { | ||
813 | if (this.FilePath != null) | ||
814 | { | ||
815 | return this.FilePath; | ||
816 | } | ||
817 | else | ||
818 | { | ||
819 | return "#" + ((int) this.Handle).ToString(CultureInfo.InvariantCulture); | ||
820 | } | ||
821 | } | ||
822 | |||
823 | /// <summary> | ||
824 | /// Closes the database handle. After closing a handle, further method calls may throw <see cref="InvalidHandleException"/>. | ||
825 | /// </summary> | ||
826 | /// <param name="disposing">If true, the method has been called directly or | ||
827 | /// indirectly by a user's code, so managed and unmanaged resources will be | ||
828 | /// disposed. If false, only unmanaged resources will be disposed.</param> | ||
829 | protected override void Dispose(bool disposing) | ||
830 | { | ||
831 | if (!this.IsClosed && | ||
832 | (this.OpenMode == DatabaseOpenMode.CreateDirect || | ||
833 | this.OpenMode == DatabaseOpenMode.Direct)) | ||
834 | { | ||
835 | // Always commit a direct-opened database before closing. | ||
836 | // This avoids unexpected corruption of the database. | ||
837 | this.Commit(); | ||
838 | } | ||
839 | |||
840 | base.Dispose(disposing); | ||
841 | |||
842 | if (disposing) | ||
843 | { | ||
844 | if (this.summaryInfo != null) | ||
845 | { | ||
846 | this.summaryInfo.Close(); | ||
847 | this.summaryInfo = null; | ||
848 | } | ||
849 | |||
850 | if (this.deleteOnClose != null) | ||
851 | { | ||
852 | foreach (string path in this.deleteOnClose) | ||
853 | { | ||
854 | try | ||
855 | { | ||
856 | if (Directory.Exists(path)) | ||
857 | { | ||
858 | Directory.Delete(path, true); | ||
859 | } | ||
860 | else | ||
861 | { | ||
862 | if (File.Exists(path)) File.Delete(path); | ||
863 | } | ||
864 | } | ||
865 | catch (IOException) | ||
866 | { | ||
867 | } | ||
868 | catch (UnauthorizedAccessException) | ||
869 | { | ||
870 | } | ||
871 | } | ||
872 | this.deleteOnClose = null; | ||
873 | } | ||
874 | } | ||
875 | } | ||
876 | |||
877 | private static int Open(string filePath, string outputPath) | ||
878 | { | ||
879 | if (String.IsNullOrEmpty(filePath)) | ||
880 | { | ||
881 | throw new ArgumentNullException("filePath"); | ||
882 | } | ||
883 | |||
884 | if (String.IsNullOrEmpty(outputPath)) | ||
885 | { | ||
886 | throw new ArgumentNullException("outputPath"); | ||
887 | } | ||
888 | |||
889 | int hDb; | ||
890 | uint ret = NativeMethods.MsiOpenDatabase(filePath, outputPath, out hDb); | ||
891 | if (ret != 0) | ||
892 | { | ||
893 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
894 | } | ||
895 | return hDb; | ||
896 | } | ||
897 | |||
898 | private static int Open(string filePath, DatabaseOpenMode mode) | ||
899 | { | ||
900 | if (String.IsNullOrEmpty(filePath)) | ||
901 | { | ||
902 | throw new ArgumentNullException("filePath"); | ||
903 | } | ||
904 | |||
905 | if (Path.GetExtension(filePath).Equals(".msp", StringComparison.Ordinal)) | ||
906 | { | ||
907 | const int DATABASEOPENMODE_PATCH = 32; | ||
908 | int patchMode = (int) mode | DATABASEOPENMODE_PATCH; | ||
909 | mode = (DatabaseOpenMode) patchMode; | ||
910 | } | ||
911 | |||
912 | int hDb; | ||
913 | uint ret = NativeMethods.MsiOpenDatabase(filePath, (IntPtr) mode, out hDb); | ||
914 | if (ret != 0) | ||
915 | { | ||
916 | throw InstallerException.ExceptionFromReturnCode( | ||
917 | ret, | ||
918 | String.Format(CultureInfo.InvariantCulture, "Database=\"{0}\"", filePath)); | ||
919 | } | ||
920 | return hDb; | ||
921 | } | ||
922 | |||
923 | /// <summary> | ||
924 | /// Returns the value of the specified property. | ||
925 | /// </summary> | ||
926 | /// <param name="property">Name of the property to retrieve.</param> | ||
927 | public string ExecutePropertyQuery(string property) | ||
928 | { | ||
929 | IList<string> values = this.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", property); | ||
930 | return (values.Count > 0 ? values[0] : null); | ||
931 | } | ||
932 | } | ||
933 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs new file mode 100644 index 00000000..7c9e011e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs | |||
@@ -0,0 +1,412 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | public partial class Database | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Gets a View object representing the query specified by a SQL string. | ||
15 | /// </summary> | ||
16 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
17 | /// <param name="args">Zero or more objects to format</param> | ||
18 | /// <returns>A View object representing the query specified by a SQL string</returns> | ||
19 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
20 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
21 | /// <remarks><p> | ||
22 | /// The <paramref name="sqlFormat"/> parameter is formatted using <see cref="String.Format(string,object[])"/>. | ||
23 | /// </p><p> | ||
24 | /// The View object should be <see cref="InstallerHandle.Close"/>d after use. | ||
25 | /// It is best that the handle be closed manually as soon as it is no longer | ||
26 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
27 | /// </p><p> | ||
28 | /// Win32 MSI API: | ||
29 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a> | ||
30 | /// </p></remarks> | ||
31 | public View OpenView(string sqlFormat, params object[] args) | ||
32 | { | ||
33 | if (String.IsNullOrEmpty(sqlFormat)) | ||
34 | { | ||
35 | throw new ArgumentNullException("sqlFormat"); | ||
36 | } | ||
37 | |||
38 | string sql = (args == null || args.Length == 0 ? sqlFormat : | ||
39 | String.Format(CultureInfo.InvariantCulture, sqlFormat, args)); | ||
40 | int viewHandle; | ||
41 | uint ret = RemotableNativeMethods.MsiDatabaseOpenView((int) this.Handle, sql, out viewHandle); | ||
42 | if (ret != 0) | ||
43 | { | ||
44 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
45 | } | ||
46 | |||
47 | return new View((IntPtr) viewHandle, sql, this); | ||
48 | } | ||
49 | |||
50 | /// <summary> | ||
51 | /// Executes the query specified by a SQL string. The query may not be a SELECT statement. | ||
52 | /// </summary> | ||
53 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
54 | /// <param name="args">Zero or more objects to format</param> | ||
55 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
56 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
57 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
58 | /// <remarks><p> | ||
59 | /// The <paramref name="sqlFormat"/> parameter is formatted using | ||
60 | /// <see cref="String.Format(string,object[])"/>. | ||
61 | /// </p><p> | ||
62 | /// Win32 MSI APIs: | ||
63 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
64 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a> | ||
65 | /// </p></remarks> | ||
66 | public void Execute(string sqlFormat, params object[] args) | ||
67 | { | ||
68 | if (String.IsNullOrEmpty(sqlFormat)) | ||
69 | { | ||
70 | throw new ArgumentNullException("sqlFormat"); | ||
71 | } | ||
72 | |||
73 | this.Execute( | ||
74 | args == null || args.Length == 0 ? | ||
75 | sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args), | ||
76 | (Record) null); | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Executes the query specified by a SQL string. The query may not be a SELECT statement. | ||
81 | /// </summary> | ||
82 | /// <param name="sql">SQL query string</param> | ||
83 | /// <param name="record">Optional Record object containing the values that replace | ||
84 | /// the parameter tokens (?) in the SQL query.</param> | ||
85 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
86 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
87 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
88 | /// <remarks><p> | ||
89 | /// Win32 MSI APIs: | ||
90 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
91 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a> | ||
92 | /// </p></remarks> | ||
93 | public void Execute(string sql, Record record) | ||
94 | { | ||
95 | if (String.IsNullOrEmpty(sql)) | ||
96 | { | ||
97 | throw new ArgumentNullException("sql"); | ||
98 | } | ||
99 | |||
100 | using (View view = this.OpenView(sql)) | ||
101 | { | ||
102 | view.Execute(record); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | /// <summary> | ||
107 | /// Executes the specified SQL SELECT query and returns all results. | ||
108 | /// </summary> | ||
109 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
110 | /// <param name="args">Zero or more objects to format</param> | ||
111 | /// <returns>All results combined into an array</returns> | ||
112 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
113 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
114 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
115 | /// <remarks><p> | ||
116 | /// The <paramref name="sqlFormat"/> parameter is formatted using | ||
117 | /// <see cref="String.Format(string,object[])"/>. | ||
118 | /// </p><p> | ||
119 | /// Multiple rows columns will be collapsed into a single one-dimensional list. | ||
120 | /// </p><p> | ||
121 | /// Win32 MSI APIs: | ||
122 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
123 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
124 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
125 | /// </p></remarks> | ||
126 | public IList ExecuteQuery(string sqlFormat, params object[] args) | ||
127 | { | ||
128 | if (String.IsNullOrEmpty(sqlFormat)) | ||
129 | { | ||
130 | throw new ArgumentNullException("sqlFormat"); | ||
131 | } | ||
132 | |||
133 | return this.ExecuteQuery( | ||
134 | args == null || args.Length == 0 ? | ||
135 | sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args), | ||
136 | (Record) null); | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Executes the specified SQL SELECT query and returns all results. | ||
141 | /// </summary> | ||
142 | /// <param name="sql">SQL SELECT query string</param> | ||
143 | /// <param name="record">Optional Record object containing the values that replace | ||
144 | /// the parameter tokens (?) in the SQL query.</param> | ||
145 | /// <returns>All results combined into an array</returns> | ||
146 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
147 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
148 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
149 | /// <remarks><p> | ||
150 | /// Multiple rows columns will be collapsed into a single one-dimensional list. | ||
151 | /// </p><p> | ||
152 | /// Win32 MSI APIs: | ||
153 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
154 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
155 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
156 | /// </p></remarks> | ||
157 | public IList ExecuteQuery(string sql, Record record) | ||
158 | { | ||
159 | if (String.IsNullOrEmpty(sql)) | ||
160 | { | ||
161 | throw new ArgumentNullException("sql"); | ||
162 | } | ||
163 | |||
164 | using (View view = this.OpenView(sql)) | ||
165 | { | ||
166 | view.Execute(record); | ||
167 | IList results = new ArrayList(); | ||
168 | int fieldCount = 0; | ||
169 | |||
170 | foreach (Record rec in view) using (rec) | ||
171 | { | ||
172 | if (fieldCount == 0) fieldCount = rec.FieldCount; | ||
173 | for (int i = 1; i <= fieldCount; i++) | ||
174 | { | ||
175 | results.Add(rec[i]); | ||
176 | } | ||
177 | } | ||
178 | |||
179 | return results; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | /// <summary> | ||
184 | /// Executes the specified SQL SELECT query and returns all results as integers. | ||
185 | /// </summary> | ||
186 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
187 | /// <param name="args">Zero or more objects to format</param> | ||
188 | /// <returns>All results combined into an array</returns> | ||
189 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
190 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
191 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
192 | /// <remarks><p> | ||
193 | /// The <paramref name="sqlFormat"/> parameter is formatted using | ||
194 | /// <see cref="String.Format(string,object[])"/>. | ||
195 | /// </p><p> | ||
196 | /// Multiple rows columns will be collapsed into a single one-dimensional list. | ||
197 | /// </p><p> | ||
198 | /// Win32 MSI APIs: | ||
199 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
200 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
201 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
202 | /// </p></remarks> | ||
203 | public IList<int> ExecuteIntegerQuery(string sqlFormat, params object[] args) | ||
204 | { | ||
205 | if (String.IsNullOrEmpty(sqlFormat)) | ||
206 | { | ||
207 | throw new ArgumentNullException("sqlFormat"); | ||
208 | } | ||
209 | |||
210 | return this.ExecuteIntegerQuery( | ||
211 | args == null || args.Length == 0 ? | ||
212 | sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args), | ||
213 | (Record) null); | ||
214 | } | ||
215 | |||
216 | /// <summary> | ||
217 | /// Executes the specified SQL SELECT query and returns all results as integers. | ||
218 | /// </summary> | ||
219 | /// <param name="sql">SQL SELECT query string</param> | ||
220 | /// <param name="record">Optional Record object containing the values that replace | ||
221 | /// the parameter tokens (?) in the SQL query.</param> | ||
222 | /// <returns>All results combined into an array</returns> | ||
223 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
224 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
225 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
226 | /// <remarks><p> | ||
227 | /// Multiple rows columns will be collapsed into a single one-dimensional list. | ||
228 | /// </p><p> | ||
229 | /// Win32 MSI APIs: | ||
230 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
231 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
232 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
233 | /// </p></remarks> | ||
234 | public IList<int> ExecuteIntegerQuery(string sql, Record record) | ||
235 | { | ||
236 | if (String.IsNullOrEmpty(sql)) | ||
237 | { | ||
238 | throw new ArgumentNullException("sql"); | ||
239 | } | ||
240 | |||
241 | using (View view = this.OpenView(sql)) | ||
242 | { | ||
243 | view.Execute(record); | ||
244 | IList<int> results = new List<int>(); | ||
245 | int fieldCount = 0; | ||
246 | |||
247 | foreach (Record rec in view) using (rec) | ||
248 | { | ||
249 | if (fieldCount == 0) fieldCount = rec.FieldCount; | ||
250 | for (int i = 1; i <= fieldCount; i++) | ||
251 | { | ||
252 | results.Add(rec.GetInteger(i)); | ||
253 | } | ||
254 | } | ||
255 | |||
256 | return results; | ||
257 | } | ||
258 | } | ||
259 | |||
260 | /// <summary> | ||
261 | /// Executes the specified SQL SELECT query and returns all results as strings. | ||
262 | /// </summary> | ||
263 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
264 | /// <param name="args">Zero or more objects to format</param> | ||
265 | /// <returns>All results combined into an array</returns> | ||
266 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
267 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
268 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
269 | /// <remarks><p> | ||
270 | /// The <paramref name="sqlFormat"/> parameter is formatted using | ||
271 | /// <see cref="String.Format(string,object[])"/>. | ||
272 | /// </p><p> | ||
273 | /// Multiple rows columns will be collapsed into a single on-dimensional list. | ||
274 | /// </p><p> | ||
275 | /// Win32 MSI APIs: | ||
276 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
277 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
278 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
279 | /// </p></remarks> | ||
280 | public IList<string> ExecuteStringQuery(string sqlFormat, params object[] args) | ||
281 | { | ||
282 | if (String.IsNullOrEmpty(sqlFormat)) | ||
283 | { | ||
284 | throw new ArgumentNullException("sqlFormat"); | ||
285 | } | ||
286 | |||
287 | return this.ExecuteStringQuery( | ||
288 | args == null || args.Length == 0 ? | ||
289 | sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args), | ||
290 | (Record) null); | ||
291 | } | ||
292 | |||
293 | /// <summary> | ||
294 | /// Executes the specified SQL SELECT query and returns all results as strings. | ||
295 | /// </summary> | ||
296 | /// <param name="sql">SQL SELECT query string</param> | ||
297 | /// <param name="record">Optional Record object containing the values that replace | ||
298 | /// the parameter tokens (?) in the SQL query.</param> | ||
299 | /// <returns>All results combined into an array</returns> | ||
300 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
301 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
302 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
303 | /// <remarks><p> | ||
304 | /// Multiple rows columns will be collapsed into a single on-dimensional list. | ||
305 | /// </p><p> | ||
306 | /// Win32 MSI APIs: | ||
307 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
308 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
309 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
310 | /// </p></remarks> | ||
311 | public IList<string> ExecuteStringQuery(string sql, Record record) | ||
312 | { | ||
313 | if (String.IsNullOrEmpty(sql)) | ||
314 | { | ||
315 | throw new ArgumentNullException("sql"); | ||
316 | } | ||
317 | |||
318 | using (View view = this.OpenView(sql)) | ||
319 | { | ||
320 | view.Execute(record); | ||
321 | IList<string> results = new List<string>(); | ||
322 | int fieldCount = 0; | ||
323 | |||
324 | foreach (Record rec in view) using (rec) | ||
325 | { | ||
326 | if (fieldCount == 0) fieldCount = rec.FieldCount; | ||
327 | for (int i = 1; i <= fieldCount; i++) | ||
328 | { | ||
329 | results.Add(rec.GetString(i)); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | return results; | ||
334 | } | ||
335 | } | ||
336 | |||
337 | /// <summary> | ||
338 | /// Executes the specified SQL SELECT query and returns a single result. | ||
339 | /// </summary> | ||
340 | /// <param name="sqlFormat">SQL query string, which may contain format items</param> | ||
341 | /// <param name="args">Zero or more objects to format</param> | ||
342 | /// <returns>First field of the first result</returns> | ||
343 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
344 | /// <exception cref="InstallerException">the View could not be executed | ||
345 | /// or the query returned 0 results</exception> | ||
346 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
347 | /// <remarks><p> | ||
348 | /// The <paramref name="sqlFormat"/> parameter is formatted using | ||
349 | /// <see cref="String.Format(string,object[])"/>. | ||
350 | /// </p><p> | ||
351 | /// Win32 MSI APIs: | ||
352 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
353 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
354 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
355 | /// </p></remarks> | ||
356 | public object ExecuteScalar(string sqlFormat, params object[] args) | ||
357 | { | ||
358 | if (String.IsNullOrEmpty(sqlFormat)) | ||
359 | { | ||
360 | throw new ArgumentNullException("sqlFormat"); | ||
361 | } | ||
362 | |||
363 | return this.ExecuteScalar( | ||
364 | args == null || args.Length == 0 ? | ||
365 | sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args), | ||
366 | (Record) null); | ||
367 | } | ||
368 | |||
369 | /// <summary> | ||
370 | /// Executes the specified SQL SELECT query and returns a single result. | ||
371 | /// </summary> | ||
372 | /// <param name="sql">SQL SELECT query string</param> | ||
373 | /// <param name="record">Optional Record object containing the values that replace | ||
374 | /// the parameter tokens (?) in the SQL query.</param> | ||
375 | /// <returns>First field of the first result</returns> | ||
376 | /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception> | ||
377 | /// <exception cref="InstallerException">the View could not be executed | ||
378 | /// or the query returned 0 results</exception> | ||
379 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
380 | /// <remarks><p> | ||
381 | /// Win32 MSI APIs: | ||
382 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>, | ||
383 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>, | ||
384 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
385 | /// </p></remarks> | ||
386 | public object ExecuteScalar(string sql, Record record) | ||
387 | { | ||
388 | if (String.IsNullOrEmpty(sql)) | ||
389 | { | ||
390 | throw new ArgumentNullException("sql"); | ||
391 | } | ||
392 | |||
393 | View view = this.OpenView(sql); | ||
394 | Record rec = null; | ||
395 | try | ||
396 | { | ||
397 | view.Execute(record); | ||
398 | rec = view.Fetch(); | ||
399 | if (rec == null) | ||
400 | { | ||
401 | throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.NO_MORE_ITEMS); | ||
402 | } | ||
403 | return rec[1]; | ||
404 | } | ||
405 | finally | ||
406 | { | ||
407 | if (rec != null) rec.Close(); | ||
408 | view.Close(); | ||
409 | } | ||
410 | } | ||
411 | } | ||
412 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs new file mode 100644 index 00000000..fa843012 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs | |||
@@ -0,0 +1,278 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Globalization; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | public partial class Database | ||
11 | { | ||
12 | /// <summary> | ||
13 | /// Creates a transform that, when applied to the object database, results in the reference database. | ||
14 | /// </summary> | ||
15 | /// <param name="referenceDatabase">Database that does not include the changes</param> | ||
16 | /// <param name="transformFile">Name of the generated transform file, or null to only | ||
17 | /// check whether or not the two database are identical</param> | ||
18 | /// <returns>true if a transform is generated, or false if a transform is not generated | ||
19 | /// because there are no differences between the two databases.</returns> | ||
20 | /// <exception cref="InstallerException">the transform could not be generated</exception> | ||
21 | /// <exception cref="InvalidHandleException">a Database handle is invalid</exception> | ||
22 | /// <remarks><p> | ||
23 | /// A transform can add non-primary key columns to the end of a table. A transform cannot | ||
24 | /// be created that adds primary key columns to a table. A transform cannot be created that | ||
25 | /// changes the order, names, or definitions of columns. | ||
26 | /// </p><p> | ||
27 | /// If the transform is to be applied during an installation you must use the | ||
28 | /// <see cref="Database.CreateTransformSummaryInfo"/> method to populate the | ||
29 | /// summary information stream. | ||
30 | /// </p><p> | ||
31 | /// Win32 MSI API: | ||
32 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasegeneratetransform.asp">MsiDatabaseGenerateTransform</a> | ||
33 | /// </p></remarks> | ||
34 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
35 | public bool GenerateTransform(Database referenceDatabase, string transformFile) | ||
36 | { | ||
37 | if (referenceDatabase == null) | ||
38 | { | ||
39 | throw new ArgumentNullException("referenceDatabase"); | ||
40 | } | ||
41 | |||
42 | if (String.IsNullOrEmpty(transformFile)) | ||
43 | { | ||
44 | throw new ArgumentNullException("transformFile"); | ||
45 | } | ||
46 | |||
47 | uint ret = NativeMethods.MsiDatabaseGenerateTransform((int) this.Handle, (int) referenceDatabase.Handle, transformFile, 0, 0); | ||
48 | if (ret == (uint) NativeMethods.Error.NO_DATA) | ||
49 | { | ||
50 | return false; | ||
51 | } | ||
52 | else if (ret != 0) | ||
53 | { | ||
54 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
55 | } | ||
56 | return true; | ||
57 | } | ||
58 | |||
59 | /// <summary> | ||
60 | /// Creates and populates the summary information stream of an existing transform file, and | ||
61 | /// fills in the properties with the base and reference ProductCode and ProductVersion. | ||
62 | /// </summary> | ||
63 | /// <param name="referenceDatabase">Database that does not include the changes</param> | ||
64 | /// <param name="transformFile">Name of the generated transform file</param> | ||
65 | /// <param name="errors">Error conditions that should be suppressed | ||
66 | /// when the transform is applied</param> | ||
67 | /// <param name="validations">Defines which properties should be validated | ||
68 | /// to verify that this transform can be applied to a database.</param> | ||
69 | /// <exception cref="InstallerException">the transform summary info could not be | ||
70 | /// generated</exception> | ||
71 | /// <exception cref="InvalidHandleException">a Database handle is invalid</exception> | ||
72 | /// <remarks><p> | ||
73 | /// Win32 MSI API: | ||
74 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreatetransformsummaryinfo.asp">MsiCreateTransformSummaryInfo</a> | ||
75 | /// </p></remarks> | ||
76 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
77 | public void CreateTransformSummaryInfo( | ||
78 | Database referenceDatabase, | ||
79 | string transformFile, | ||
80 | TransformErrors errors, | ||
81 | TransformValidations validations) | ||
82 | { | ||
83 | if (referenceDatabase == null) | ||
84 | { | ||
85 | throw new ArgumentNullException("referenceDatabase"); | ||
86 | } | ||
87 | |||
88 | if (String.IsNullOrEmpty(transformFile)) | ||
89 | { | ||
90 | throw new ArgumentNullException("transformFile"); | ||
91 | } | ||
92 | |||
93 | uint ret = NativeMethods.MsiCreateTransformSummaryInfo( | ||
94 | (int) this.Handle, | ||
95 | (int) referenceDatabase.Handle, | ||
96 | transformFile, | ||
97 | (int) errors, | ||
98 | (int) validations); | ||
99 | if (ret != 0) | ||
100 | { | ||
101 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /// <summary> | ||
106 | /// Apply a transform to the database, recording the changes in the "_TransformView" table. | ||
107 | /// </summary> | ||
108 | /// <param name="transformFile">Path to the transform file</param> | ||
109 | /// <exception cref="InstallerException">the transform could not be applied</exception> | ||
110 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
111 | /// <remarks><p> | ||
112 | /// Win32 MSI API: | ||
113 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a> | ||
114 | /// </p></remarks> | ||
115 | public void ViewTransform(string transformFile) | ||
116 | { | ||
117 | TransformErrors transformErrors = | ||
118 | TransformErrors.AddExistingRow | | ||
119 | TransformErrors.DelMissingRow | | ||
120 | TransformErrors.AddExistingTable | | ||
121 | TransformErrors.DelMissingTable | | ||
122 | TransformErrors.UpdateMissingRow | | ||
123 | TransformErrors.ChangeCodePage | | ||
124 | TransformErrors.ViewTransform; | ||
125 | this.ApplyTransform(transformFile, transformErrors); | ||
126 | } | ||
127 | |||
128 | /// <summary> | ||
129 | /// Apply a transform to the database, suppressing any error conditions | ||
130 | /// specified by the transform's summary information. | ||
131 | /// </summary> | ||
132 | /// <param name="transformFile">Path to the transform file</param> | ||
133 | /// <exception cref="InstallerException">the transform could not be applied</exception> | ||
134 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
135 | /// <remarks><p> | ||
136 | /// Win32 MSI API: | ||
137 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a> | ||
138 | /// </p></remarks> | ||
139 | public void ApplyTransform(string transformFile) | ||
140 | { | ||
141 | if (String.IsNullOrEmpty(transformFile)) | ||
142 | { | ||
143 | throw new ArgumentNullException("transformFile"); | ||
144 | } | ||
145 | |||
146 | TransformErrors errorConditionsToSuppress; | ||
147 | using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false)) | ||
148 | { | ||
149 | int errorConditions = transformSummInfo.CharacterCount & 0xFFFF; | ||
150 | errorConditionsToSuppress = (TransformErrors) errorConditions; | ||
151 | } | ||
152 | this.ApplyTransform(transformFile, errorConditionsToSuppress); | ||
153 | } | ||
154 | |||
155 | /// <summary> | ||
156 | /// Apply a transform to the database, specifying error conditions to suppress. | ||
157 | /// </summary> | ||
158 | /// <param name="transformFile">Path to the transform file</param> | ||
159 | /// <param name="errorConditionsToSuppress">Error conditions that are to be suppressed</param> | ||
160 | /// <exception cref="InstallerException">the transform could not be applied</exception> | ||
161 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
162 | /// <remarks><p> | ||
163 | /// Win32 MSI API: | ||
164 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a> | ||
165 | /// </p></remarks> | ||
166 | public void ApplyTransform(string transformFile, TransformErrors errorConditionsToSuppress) | ||
167 | { | ||
168 | if (String.IsNullOrEmpty(transformFile)) | ||
169 | { | ||
170 | throw new ArgumentNullException("transformFile"); | ||
171 | } | ||
172 | |||
173 | uint ret = NativeMethods.MsiDatabaseApplyTransform((int) this.Handle, transformFile, (int) errorConditionsToSuppress); | ||
174 | if (ret != 0) | ||
175 | { | ||
176 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
177 | } | ||
178 | } | ||
179 | |||
180 | /// <summary> | ||
181 | /// Checks whether a transform is valid for this Database, according to its validation data and flags. | ||
182 | /// </summary> | ||
183 | /// <param name="transformFile">Path to the transform file</param> | ||
184 | /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns> | ||
185 | /// <exception cref="InstallerException">the transform could not be applied</exception> | ||
186 | /// <exception cref="InvalidHandleException">the Database handle is invalid</exception> | ||
187 | public bool IsTransformValid(string transformFile) | ||
188 | { | ||
189 | if (String.IsNullOrEmpty(transformFile)) | ||
190 | { | ||
191 | throw new ArgumentNullException("transformFile"); | ||
192 | } | ||
193 | |||
194 | using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false)) | ||
195 | { | ||
196 | return this.IsTransformValid(transformSummInfo); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | /// <summary> | ||
201 | /// Checks whether a transform is valid for this Database, according to its SummaryInfo data. | ||
202 | /// </summary> | ||
203 | /// <param name="transformSummaryInfo">SummaryInfo data of a transform file</param> | ||
204 | /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns> | ||
205 | /// <exception cref="InstallerException">error processing summary info</exception> | ||
206 | /// <exception cref="InvalidHandleException">the Database or SummaryInfo handle is invalid</exception> | ||
207 | public bool IsTransformValid(SummaryInfo transformSummaryInfo) | ||
208 | { | ||
209 | if (transformSummaryInfo == null) | ||
210 | { | ||
211 | throw new ArgumentNullException("transformSummaryInfo"); | ||
212 | } | ||
213 | |||
214 | string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3); | ||
215 | string targetProductCode = rev[0].Substring(0, 38); | ||
216 | string targetProductVersion = rev[0].Substring(38); | ||
217 | string upgradeCode = rev[2]; | ||
218 | |||
219 | string[] templ = transformSummaryInfo.Template.Split(new char[] { ';' }, 2); | ||
220 | int targetProductLanguage = 0; | ||
221 | if (templ.Length >= 2 && templ[1].Length > 0) | ||
222 | { | ||
223 | targetProductLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat); | ||
224 | } | ||
225 | |||
226 | int flags = transformSummaryInfo.CharacterCount; | ||
227 | int validateFlags = flags >> 16; | ||
228 | |||
229 | string thisProductCode = this.ExecutePropertyQuery("ProductCode"); | ||
230 | string thisProductVersion = this.ExecutePropertyQuery("ProductVersion"); | ||
231 | string thisUpgradeCode = this.ExecutePropertyQuery("UpgradeCode"); | ||
232 | string thisProductLang = this.ExecutePropertyQuery("ProductLanguage"); | ||
233 | int thisProductLanguage = 0; | ||
234 | if (!String.IsNullOrEmpty(thisProductLang)) | ||
235 | { | ||
236 | thisProductLanguage = Int32.Parse(thisProductLang, CultureInfo.InvariantCulture.NumberFormat); | ||
237 | } | ||
238 | |||
239 | if ((validateFlags & (int) TransformValidations.Product) != 0 && | ||
240 | thisProductCode != targetProductCode) | ||
241 | { | ||
242 | return false; | ||
243 | } | ||
244 | |||
245 | if ((validateFlags & (int) TransformValidations.UpgradeCode) != 0 && | ||
246 | thisUpgradeCode != upgradeCode) | ||
247 | { | ||
248 | return false; | ||
249 | } | ||
250 | |||
251 | if ((validateFlags & (int) TransformValidations.Language) != 0 && | ||
252 | targetProductLanguage != 0 && thisProductLanguage != targetProductLanguage) | ||
253 | { | ||
254 | return false; | ||
255 | } | ||
256 | |||
257 | Version thisProductVer = new Version(thisProductVersion); | ||
258 | Version targetProductVer = new Version(targetProductVersion); | ||
259 | if ((validateFlags & (int) TransformValidations.UpdateVersion) != 0) | ||
260 | { | ||
261 | if (thisProductVer.Major != targetProductVer.Major) return false; | ||
262 | if (thisProductVer.Minor != targetProductVer.Minor) return false; | ||
263 | if (thisProductVer.Build != targetProductVer.Build) return false; | ||
264 | } | ||
265 | else if ((validateFlags & (int) TransformValidations.MinorVersion) != 0) | ||
266 | { | ||
267 | if (thisProductVer.Major != targetProductVer.Major) return false; | ||
268 | if (thisProductVer.Minor != targetProductVer.Minor) return false; | ||
269 | } | ||
270 | else if ((validateFlags & (int) TransformValidations.MajorVersion) != 0) | ||
271 | { | ||
272 | if (thisProductVer.Major != targetProductVer.Major) return false; | ||
273 | } | ||
274 | |||
275 | return true; | ||
276 | } | ||
277 | } | ||
278 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs new file mode 100644 index 00000000..05e910d4 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs | |||
@@ -0,0 +1,231 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Configuration; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.IO; | ||
10 | using System.Reflection; | ||
11 | using System.Runtime.InteropServices; | ||
12 | using System.Security; | ||
13 | using System.Text; | ||
14 | |||
15 | /// <summary> | ||
16 | /// Managed-code portion of the embedded UI proxy. | ||
17 | /// </summary> | ||
18 | internal static class EmbeddedUIProxy | ||
19 | { | ||
20 | private static IEmbeddedUI uiInstance; | ||
21 | private static string uiClass; | ||
22 | |||
23 | private static bool DebugBreakEnabled(string method) | ||
24 | { | ||
25 | return CustomActionProxy.DebugBreakEnabled(new string[] { method, EmbeddedUIProxy.uiClass + "." + method } ); | ||
26 | } | ||
27 | |||
28 | /// <summary> | ||
29 | /// Initializes managed embedded UI by loading the UI class and invoking its Initialize method. | ||
30 | /// </summary> | ||
31 | /// <param name="sessionHandle">Integer handle to the installer session.</param> | ||
32 | /// <param name="uiClass">Name of the class that implements the embedded UI. This must | ||
33 | /// be of the form: "AssemblyName!Namespace.Class"</param> | ||
34 | /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this | ||
35 | /// method returns, the installer resets the UI level to the returned value of this parameter.</param> | ||
36 | /// <returns>0 if the embedded UI was successfully loaded and initialized, | ||
37 | /// ERROR_INSTALL_USEREXIT if the user canceled the installation during initialization, | ||
38 | /// or ERROR_INSTALL_FAILURE if the embedded UI could not be initialized.</returns> | ||
39 | /// <remarks> | ||
40 | /// Due to interop limitations, the successful resulting UILevel is actually returned | ||
41 | /// as the high-word of the return value instead of via a ref parameter. | ||
42 | /// </remarks> | ||
43 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
44 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
45 | public static int Initialize(int sessionHandle, string uiClass, int internalUILevel) | ||
46 | { | ||
47 | Session session = null; | ||
48 | |||
49 | try | ||
50 | { | ||
51 | session = new Session((IntPtr) sessionHandle, false); | ||
52 | |||
53 | if (String.IsNullOrEmpty(uiClass)) | ||
54 | { | ||
55 | throw new ArgumentNullException("uiClass"); | ||
56 | } | ||
57 | |||
58 | EmbeddedUIProxy.uiInstance = EmbeddedUIProxy.InstantiateUI(session, uiClass); | ||
59 | } | ||
60 | catch (Exception ex) | ||
61 | { | ||
62 | if (session != null) | ||
63 | { | ||
64 | try | ||
65 | { | ||
66 | session.Log("Exception while loading embedded UI:"); | ||
67 | session.Log(ex.ToString()); | ||
68 | } | ||
69 | catch (Exception) | ||
70 | { | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | if (EmbeddedUIProxy.uiInstance == null) | ||
76 | { | ||
77 | return (int) ActionResult.Failure; | ||
78 | } | ||
79 | |||
80 | try | ||
81 | { | ||
82 | string resourcePath = Path.GetDirectoryName(EmbeddedUIProxy.uiInstance.GetType().Assembly.Location); | ||
83 | InstallUIOptions uiOptions = (InstallUIOptions) internalUILevel; | ||
84 | if (EmbeddedUIProxy.DebugBreakEnabled("Initialize")) | ||
85 | { | ||
86 | System.Diagnostics.Debugger.Launch(); | ||
87 | } | ||
88 | |||
89 | if (EmbeddedUIProxy.uiInstance.Initialize(session, resourcePath, ref uiOptions)) | ||
90 | { | ||
91 | // The embedded UI initialized and the installation should continue | ||
92 | // with internal UI reset according to options. | ||
93 | return ((int) uiOptions) << 16; | ||
94 | } | ||
95 | else | ||
96 | { | ||
97 | // The embedded UI did not initialize but the installation should still continue | ||
98 | // with internal UI reset according to options. | ||
99 | return (int) uiOptions; | ||
100 | } | ||
101 | } | ||
102 | catch (InstallCanceledException) | ||
103 | { | ||
104 | // The installation was canceled by the user. | ||
105 | return (int) ActionResult.UserExit; | ||
106 | } | ||
107 | catch (Exception ex) | ||
108 | { | ||
109 | // An unhandled exception causes the installation to fail immediately. | ||
110 | session.Log("Exception thrown by embedded UI initialization:"); | ||
111 | session.Log(ex.ToString()); | ||
112 | return (int) ActionResult.Failure; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | /// <summary> | ||
117 | /// Passes a progress message to the UI class. | ||
118 | /// </summary> | ||
119 | /// <param name="messageType">Installer message type and message box options.</param> | ||
120 | /// <param name="recordHandle">Handle to a record containing message data.</param> | ||
121 | /// <returns>Return value returned by the UI class.</returns> | ||
122 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
123 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
124 | public static int ProcessMessage(int messageType, int recordHandle) | ||
125 | { | ||
126 | if (EmbeddedUIProxy.uiInstance != null) | ||
127 | { | ||
128 | try | ||
129 | { | ||
130 | int msgType = messageType & 0x7F000000; | ||
131 | int buttons = messageType & 0x0000000F; | ||
132 | int icon = messageType & 0x000000F0; | ||
133 | int defButton = messageType & 0x00000F00; | ||
134 | |||
135 | Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null); | ||
136 | using (msgRec) | ||
137 | { | ||
138 | if (EmbeddedUIProxy.DebugBreakEnabled("ProcessMessage")) | ||
139 | { | ||
140 | System.Diagnostics.Debugger.Launch(); | ||
141 | } | ||
142 | |||
143 | return (int) EmbeddedUIProxy.uiInstance.ProcessMessage( | ||
144 | (InstallMessage) msgType, | ||
145 | msgRec, | ||
146 | (MessageButtons) buttons, | ||
147 | (MessageIcon) icon, | ||
148 | (MessageDefaultButton) defButton); | ||
149 | } | ||
150 | } | ||
151 | catch (Exception) | ||
152 | { | ||
153 | // Ignore it... just hope future messages will not throw exceptions. | ||
154 | } | ||
155 | } | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | /// <summary> | ||
161 | /// Passes a shutdown message to the UI class. | ||
162 | /// </summary> | ||
163 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
164 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
165 | public static void Shutdown() | ||
166 | { | ||
167 | if (EmbeddedUIProxy.uiInstance != null) | ||
168 | { | ||
169 | try | ||
170 | { | ||
171 | if (EmbeddedUIProxy.DebugBreakEnabled("Shutdown")) | ||
172 | { | ||
173 | System.Diagnostics.Debugger.Launch(); | ||
174 | } | ||
175 | |||
176 | EmbeddedUIProxy.uiInstance.Shutdown(); | ||
177 | } | ||
178 | catch (Exception) | ||
179 | { | ||
180 | // Nothing to do at this point... the installation is done anyway. | ||
181 | } | ||
182 | |||
183 | EmbeddedUIProxy.uiInstance = null; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Instantiates a UI class from a given assembly and class name. | ||
189 | /// </summary> | ||
190 | /// <param name="session">Installer session, for logging.</param> | ||
191 | /// <param name="uiClass">Name of the class that implements the embedded UI. This must | ||
192 | /// be of the form: "AssemblyName!Namespace.Class"</param> | ||
193 | /// <returns>Interface on the UI class for handling UI messages.</returns> | ||
194 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
195 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
196 | private static IEmbeddedUI InstantiateUI(Session session, string uiClass) | ||
197 | { | ||
198 | int assemblySplit = uiClass.IndexOf('!'); | ||
199 | if (assemblySplit < 0) | ||
200 | { | ||
201 | session.Log("Error: invalid embedded UI assembly and class:" + uiClass); | ||
202 | return null; | ||
203 | } | ||
204 | |||
205 | string assemblyName = uiClass.Substring(0, assemblySplit); | ||
206 | EmbeddedUIProxy.uiClass = uiClass.Substring(assemblySplit + 1); | ||
207 | |||
208 | Assembly uiAssembly; | ||
209 | try | ||
210 | { | ||
211 | uiAssembly = AppDomain.CurrentDomain.Load(assemblyName); | ||
212 | |||
213 | // This calls out to CustomActionProxy.DebugBreakEnabled() directly instead | ||
214 | // of calling EmbeddedUIProxy.DebugBreakEnabled() because we don't compose a | ||
215 | // class.method name for this breakpoint. | ||
216 | if (CustomActionProxy.DebugBreakEnabled(new string[] { "EmbeddedUI" })) | ||
217 | { | ||
218 | System.Diagnostics.Debugger.Launch(); | ||
219 | } | ||
220 | |||
221 | return (IEmbeddedUI) uiAssembly.CreateInstance(EmbeddedUIProxy.uiClass); | ||
222 | } | ||
223 | catch (Exception ex) | ||
224 | { | ||
225 | session.Log("Error: could not load embedded UI class " + EmbeddedUIProxy.uiClass + " from assembly: " + assemblyName); | ||
226 | session.Log(ex.ToString()); | ||
227 | return null; | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs new file mode 100644 index 00000000..64ed0e7f --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs | |||
@@ -0,0 +1,909 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics.CodeAnalysis; | ||
7 | |||
8 | // Enumerations are in alphabetical order. | ||
9 | |||
10 | /// <summary> | ||
11 | /// Specifies a return status value for custom actions. | ||
12 | /// </summary> | ||
13 | public enum ActionResult : int | ||
14 | { | ||
15 | /// <summary>Action completed successfully.</summary> | ||
16 | Success = 0, | ||
17 | |||
18 | /// <summary>Skip remaining actions, not an error.</summary> | ||
19 | SkipRemainingActions = 259, | ||
20 | |||
21 | /// <summary>User terminated prematurely.</summary> | ||
22 | UserExit = 1602, | ||
23 | |||
24 | /// <summary>Unrecoverable error or unhandled exception occurred.</summary> | ||
25 | Failure = 1603, | ||
26 | |||
27 | /// <summary>Action not executed.</summary> | ||
28 | NotExecuted = 1626, | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Specifies the open mode for a <see cref="Database"/>. | ||
33 | /// </summary> | ||
34 | public enum DatabaseOpenMode : int | ||
35 | { | ||
36 | /// <summary>Open a database read-only, no persistent changes.</summary> | ||
37 | ReadOnly = 0, | ||
38 | |||
39 | /// <summary>Open a database read/write in transaction mode.</summary> | ||
40 | Transact = 1, | ||
41 | |||
42 | /// <summary>Open a database direct read/write without transaction.</summary> | ||
43 | Direct = 2, | ||
44 | |||
45 | /// <summary>Create a new database, transact mode read/write.</summary> | ||
46 | Create = 3, | ||
47 | |||
48 | /// <summary>Create a new database, direct mode read/write.</summary> | ||
49 | CreateDirect = 4, | ||
50 | } | ||
51 | |||
52 | /// <summary> | ||
53 | /// Log modes available for <see cref="Installer.EnableLog(InstallLogModes,string)"/> | ||
54 | /// and <see cref="Installer.SetExternalUI(ExternalUIHandler,InstallLogModes)"/>. | ||
55 | /// </summary> | ||
56 | [Flags] | ||
57 | public enum InstallLogModes : int | ||
58 | { | ||
59 | /// <summary>Disable logging.</summary> | ||
60 | None = 0, | ||
61 | |||
62 | /// <summary>Log out of memory or fatal exit information.</summary> | ||
63 | FatalExit = (1 << ((int) InstallMessage.FatalExit >> 24)), | ||
64 | |||
65 | /// <summary>Log error messages.</summary> | ||
66 | Error = (1 << ((int) InstallMessage.Error >> 24)), | ||
67 | |||
68 | /// <summary>Log warning messages.</summary> | ||
69 | Warning = (1 << ((int) InstallMessage.Warning >> 24)), | ||
70 | |||
71 | /// <summary>Log user requests.</summary> | ||
72 | User = (1 << ((int) InstallMessage.User >> 24)), | ||
73 | |||
74 | /// <summary>Log status messages that are not displayed.</summary> | ||
75 | Info = (1 << ((int) InstallMessage.Info >> 24)), | ||
76 | |||
77 | /// <summary>Log request to determine a valid source location.</summary> | ||
78 | ResolveSource = (1 << ((int) InstallMessage.ResolveSource >> 24)), | ||
79 | |||
80 | /// <summary>Log insufficient disk space error.</summary> | ||
81 | OutOfDiskSpace = (1 << ((int) InstallMessage.OutOfDiskSpace >> 24)), | ||
82 | |||
83 | /// <summary>Log the start of installation actions.</summary> | ||
84 | ActionStart = (1 << ((int) InstallMessage.ActionStart >> 24)), | ||
85 | |||
86 | /// <summary>Log the data record for installation actions.</summary> | ||
87 | ActionData = (1 << ((int) InstallMessage.ActionData >> 24)), | ||
88 | |||
89 | /// <summary>Log parameters for user-interface initialization.</summary> | ||
90 | CommonData = (1 << ((int) InstallMessage.CommonData >> 24)), | ||
91 | |||
92 | /// <summary>Log the property values at termination.</summary> | ||
93 | PropertyDump = (1 << ((int) InstallMessage.Progress >> 24)), // log only | ||
94 | |||
95 | /// <summary> | ||
96 | /// Sends large amounts of information to log file not generally useful to users. | ||
97 | /// May be used for support. | ||
98 | /// </summary> | ||
99 | Verbose = (1 << ((int) InstallMessage.Initialize >> 24)), // log only | ||
100 | |||
101 | /// <summary> | ||
102 | /// Log extra debugging information. | ||
103 | /// </summary> | ||
104 | ExtraDebug = (1 << ((int) InstallMessage.Terminate >> 24)), // log only | ||
105 | |||
106 | /// <summary> | ||
107 | /// Log only on error. | ||
108 | /// </summary> | ||
109 | LogOnlyOnError = (1 << ((int) InstallMessage.ShowDialog >> 24)), // log only | ||
110 | |||
111 | /// <summary> | ||
112 | /// Log progress bar information. This message includes information on units so far and total number | ||
113 | /// of units. See <see cref="Session.Message"/> for an explanation of the message format. This message | ||
114 | /// is only sent to an external user interface and is not logged. | ||
115 | /// </summary> | ||
116 | Progress = (1 << ((int) InstallMessage.Progress >> 24)), // external handler only | ||
117 | |||
118 | /// <summary> | ||
119 | /// If this is not a quiet installation, then the basic UI has been initialized. If this is a full | ||
120 | /// UI installation, the Full UI is not yet initialized. This message is only sent to an external | ||
121 | /// user interface and is not logged. | ||
122 | /// </summary> | ||
123 | Initialize = (1 << ((int) InstallMessage.Initialize >> 24)), // external handler only | ||
124 | |||
125 | /// <summary> | ||
126 | /// If a full UI is being used, the full UI has ended. If this is not a quiet installation, the basic | ||
127 | /// UI has not yet ended. This message is only sent to an external user interface and is not logged. | ||
128 | /// </summary> | ||
129 | Terminate = (1 << ((int) InstallMessage.Terminate >> 24)), // external handler only | ||
130 | |||
131 | /// <summary> | ||
132 | /// Sent prior to display of the Full UI dialog. This message is only sent to an external user | ||
133 | /// interface and is not logged. | ||
134 | /// </summary> | ||
135 | ShowDialog = (1 << ((int) InstallMessage.ShowDialog >> 24)), // external handler only | ||
136 | |||
137 | /// <summary> | ||
138 | /// List of files in use that need to be replaced. | ||
139 | /// </summary> | ||
140 | FilesInUse = (1 << ((int) InstallMessage.FilesInUse >> 24)), // external handler only | ||
141 | |||
142 | /// <summary> | ||
143 | /// [MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart. | ||
144 | /// </summary> | ||
145 | RMFilesInUse = (1 << ((int) InstallMessage.RMFilesInUse >> 24)), // external handler only | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Type of message to be processed by <see cref="Session.Message"/>, | ||
150 | /// <see cref="ExternalUIHandler"/>, or <see cref="ExternalUIRecordHandler"/>. | ||
151 | /// </summary> | ||
152 | [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] | ||
153 | public enum InstallMessage : int | ||
154 | { | ||
155 | /// <summary>Premature termination, possibly fatal OOM.</summary> | ||
156 | FatalExit = 0x00000000, | ||
157 | |||
158 | /// <summary>Formatted error message.</summary> | ||
159 | Error = 0x01000000, | ||
160 | |||
161 | /// <summary>Formatted warning message.</summary> | ||
162 | Warning = 0x02000000, | ||
163 | |||
164 | /// <summary>User request message.</summary> | ||
165 | User = 0x03000000, | ||
166 | |||
167 | /// <summary>Informative message for log.</summary> | ||
168 | Info = 0x04000000, | ||
169 | |||
170 | /// <summary>List of files in use that need to be replaced.</summary> | ||
171 | FilesInUse = 0x05000000, | ||
172 | |||
173 | /// <summary>Request to determine a valid source location.</summary> | ||
174 | ResolveSource = 0x06000000, | ||
175 | |||
176 | /// <summary>Insufficient disk space message.</summary> | ||
177 | OutOfDiskSpace = 0x07000000, | ||
178 | |||
179 | /// <summary>Start of action: action name & description.</summary> | ||
180 | ActionStart = 0x08000000, | ||
181 | |||
182 | /// <summary>Formatted data associated with individual action item.</summary> | ||
183 | ActionData = 0x09000000, | ||
184 | |||
185 | /// <summary>Progress gauge info: units so far, total.</summary> | ||
186 | Progress = 0x0A000000, | ||
187 | |||
188 | /// <summary>Product info for dialog: language Id, dialog caption.</summary> | ||
189 | CommonData = 0x0B000000, | ||
190 | |||
191 | /// <summary>Sent prior to UI initialization, no string data.</summary> | ||
192 | Initialize = 0x0C000000, | ||
193 | |||
194 | /// <summary>Sent after UI termination, no string data.</summary> | ||
195 | Terminate = 0x0D000000, | ||
196 | |||
197 | /// <summary>Sent prior to display or authored dialog or wizard.</summary> | ||
198 | ShowDialog = 0x0E000000, | ||
199 | |||
200 | /// <summary>[MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart.</summary> | ||
201 | RMFilesInUse = 0x19000000, | ||
202 | |||
203 | /// <summary>[MSI 4.5] Sent prior to install of a product.</summary> | ||
204 | InstallStart = 0x1A000000, | ||
205 | |||
206 | /// <summary>[MSI 4.5] Sent after install of a product.</summary> | ||
207 | InstallEnd = 0x1B000000, | ||
208 | } | ||
209 | |||
210 | /// <summary> | ||
211 | /// Specifies the install mode for <see cref="Installer.ProvideComponent"/> or <see cref="Installer.ProvideQualifiedComponent"/>. | ||
212 | /// </summary> | ||
213 | public enum InstallMode : int | ||
214 | { | ||
215 | /// <summary>Provide the component only if the feature's installation state is <see cref="InstallState.Local"/>.</summary> | ||
216 | NoSourceResolution = -3, | ||
217 | |||
218 | /// <summary>Only check that the component is registered, without verifying that the key file of the component exists.</summary> | ||
219 | NoDetection = -2, | ||
220 | |||
221 | /// <summary>Provide the component only if the feature exists.</summary> | ||
222 | Existing = -1, | ||
223 | |||
224 | /// <summary>Provide the component and perform any installation necessary to provide the component.</summary> | ||
225 | Default = 0, | ||
226 | } | ||
227 | |||
228 | /// <summary> | ||
229 | /// Specifies the run mode for <see cref="Session.GetMode"/>. | ||
230 | /// </summary> | ||
231 | [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] | ||
232 | public enum InstallRunMode : int | ||
233 | { | ||
234 | /// <summary>The administrative mode is installing, or the product is installing.</summary> | ||
235 | Admin = 0, | ||
236 | |||
237 | /// <summary>The advertisements are installing or the product is installing or updating.</summary> | ||
238 | Advertise = 1, | ||
239 | |||
240 | /// <summary>An existing installation is being modified or there is a new installation.</summary> | ||
241 | Maintenance = 2, | ||
242 | |||
243 | /// <summary>Rollback is enabled.</summary> | ||
244 | RollbackEnabled = 3, | ||
245 | |||
246 | /// <summary>The log file is active. It was enabled prior to the installation session.</summary> | ||
247 | LogEnabled = 4, | ||
248 | |||
249 | /// <summary>Execute operations are spooling or they are in the determination phase.</summary> | ||
250 | Operations = 5, | ||
251 | |||
252 | /// <summary>A reboot is necessary after a successful installation (settable).</summary> | ||
253 | RebootAtEnd = 6, | ||
254 | |||
255 | /// <summary>A reboot is necessary to continue the installation (settable).</summary> | ||
256 | RebootNow = 7, | ||
257 | |||
258 | /// <summary>Files from cabinets and Media table files are installing.</summary> | ||
259 | Cabinet = 8, | ||
260 | |||
261 | /// <summary>The source LongFileNames is suppressed through the PID_MSISOURCE summary property.</summary> | ||
262 | SourceShortNames = 9, | ||
263 | |||
264 | /// <summary>The target LongFileNames is suppressed through the SHORTFILENAMES property.</summary> | ||
265 | TargetShortNames = 10, | ||
266 | |||
267 | // <summary>Reserved for future use.</summary> | ||
268 | //Reserved11 = 11, | ||
269 | |||
270 | /// <summary>The operating system is Windows 95, Windows 98, or Windows ME.</summary> | ||
271 | [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x")] | ||
272 | Windows9x = 12, | ||
273 | |||
274 | /// <summary>The operating system supports demand installation.</summary> | ||
275 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zaw")] | ||
276 | ZawEnabled = 13, | ||
277 | |||
278 | // <summary>Reserved for future use.</summary> | ||
279 | //Reserved14 = 14, | ||
280 | |||
281 | // <summary>Reserved for future use.</summary> | ||
282 | //Reserved15 = 15, | ||
283 | |||
284 | /// <summary>A custom action called from install script execution.</summary> | ||
285 | Scheduled = 16, | ||
286 | |||
287 | /// <summary>A custom action called from rollback execution script.</summary> | ||
288 | Rollback = 17, | ||
289 | |||
290 | /// <summary>A custom action called from commit execution script.</summary> | ||
291 | Commit = 18, | ||
292 | } | ||
293 | |||
294 | /// <summary> | ||
295 | /// Installed state of a Component or Feature. | ||
296 | /// </summary> | ||
297 | public enum InstallState : int | ||
298 | { | ||
299 | /// <summary>The component is disabled.</summary> | ||
300 | NotUsed = -7, | ||
301 | |||
302 | /// <summary>The installation configuration data is corrupt.</summary> | ||
303 | BadConfig = -6, | ||
304 | |||
305 | /// <summary>The installation is suspended or in progress.</summary> | ||
306 | Incomplete = -5, | ||
307 | |||
308 | /// <summary>Component is set to run from source, but source is unavailable.</summary> | ||
309 | SourceAbsent = -4, | ||
310 | |||
311 | /// <summary>The buffer overflow is returned.</summary> | ||
312 | MoreData = -3, | ||
313 | |||
314 | /// <summary>An invalid parameter was passed to the function.</summary> | ||
315 | InvalidArgument = -2, | ||
316 | |||
317 | /// <summary>An unrecognized product or feature name was passed to the function.</summary> | ||
318 | Unknown = -1, | ||
319 | |||
320 | /// <summary>The component is broken.</summary> | ||
321 | Broken = 0, | ||
322 | |||
323 | /// <summary>The feature is advertised.</summary> | ||
324 | Advertised = 1, | ||
325 | |||
326 | /// <summary>The component is being removed. In action state and not settable.</summary> | ||
327 | Removed = 1, | ||
328 | |||
329 | /// <summary>The component is not installed, or action state is absent but clients remain.</summary> | ||
330 | Absent = 2, | ||
331 | |||
332 | /// <summary>The component is installed on the local drive.</summary> | ||
333 | Local = 3, | ||
334 | |||
335 | /// <summary>The component will run from the source, CD, or network.</summary> | ||
336 | Source = 4, | ||
337 | |||
338 | /// <summary>The component will be installed in the default location: local or source.</summary> | ||
339 | Default = 5, | ||
340 | } | ||
341 | |||
342 | /// <summary> | ||
343 | /// Specifies the type of installation for <see cref="Installer.ApplyPatch(string,string,InstallType,string)"/>. | ||
344 | /// </summary> | ||
345 | public enum InstallType : int | ||
346 | { | ||
347 | /// <summary>Searches system for products to patch.</summary> | ||
348 | Default = 0, | ||
349 | |||
350 | /// <summary>Indicates a administrative installation.</summary> | ||
351 | NetworkImage = 1, | ||
352 | |||
353 | /// <summary>Indicates a particular instance.</summary> | ||
354 | SingleInstance = 2, | ||
355 | } | ||
356 | |||
357 | /// <summary> | ||
358 | /// Level of the installation user interface, specified with | ||
359 | /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/>. | ||
360 | /// </summary> | ||
361 | [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] | ||
362 | [Flags] | ||
363 | public enum InstallUIOptions : int | ||
364 | { | ||
365 | /// <summary>Does not change UI level.</summary> | ||
366 | NoChange = 0, | ||
367 | |||
368 | /// <summary>Uses Default UI level.</summary> | ||
369 | Default = 1, | ||
370 | |||
371 | /// <summary>Silent installation.</summary> | ||
372 | Silent = 2, | ||
373 | |||
374 | /// <summary>Simple progress and error handling.</summary> | ||
375 | Basic = 3, | ||
376 | |||
377 | /// <summary>Authored UI, wizard dialogs suppressed.</summary> | ||
378 | Reduced = 4, | ||
379 | |||
380 | /// <summary>Authored UI with wizards, progress, and errors.</summary> | ||
381 | Full = 5, | ||
382 | |||
383 | /// <summary> | ||
384 | /// When combined with the <see cref="Basic"/> value, the installer does not display | ||
385 | /// the cancel button in the progress dialog. | ||
386 | /// </summary> | ||
387 | HideCancel = 0x20, | ||
388 | |||
389 | /// <summary> | ||
390 | /// When combined with the <see cref="Basic"/> value, the installer displays progress | ||
391 | /// dialog boxes but does not display any modal dialog boxes or error dialog boxes. | ||
392 | /// </summary> | ||
393 | ProgressOnly = 0x40, | ||
394 | |||
395 | /// <summary> | ||
396 | /// When combined with another value, the installer displays a modal dialog | ||
397 | /// box at the end of a successful installation or if there has been an error. | ||
398 | /// No dialog box is displayed if the user cancels. | ||
399 | /// </summary> | ||
400 | EndDialog = 0x80, | ||
401 | |||
402 | /// <summary> | ||
403 | /// Forces display of the source resolution dialog even if the UI is otherwise silent. | ||
404 | /// </summary> | ||
405 | SourceResolutionOnly = 0x100, | ||
406 | |||
407 | /// <summary> | ||
408 | /// [MSI 5.0] Forces display of the UAC dialog even if the UI is otherwise silent. | ||
409 | /// </summary> | ||
410 | UacOnly = 0x200, | ||
411 | } | ||
412 | |||
413 | /// <summary> | ||
414 | /// Specifies a return status value for message handlers. These values are returned by | ||
415 | /// <see cref="Session.Message"/>, <see cref="ExternalUIHandler"/>, and <see cref="IEmbeddedUI.ProcessMessage"/>. | ||
416 | /// </summary> | ||
417 | public enum MessageResult : int | ||
418 | { | ||
419 | /// <summary>An error was found in the message handler.</summary> | ||
420 | Error = -1, | ||
421 | |||
422 | /// <summary>No action was taken.</summary> | ||
423 | None = 0, | ||
424 | |||
425 | /// <summary>IDOK</summary> | ||
426 | [SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase")] | ||
427 | OK = 1, | ||
428 | |||
429 | /// <summary>IDCANCEL</summary> | ||
430 | Cancel = 2, | ||
431 | |||
432 | /// <summary>IDABORT</summary> | ||
433 | Abort = 3, | ||
434 | |||
435 | /// <summary>IDRETRY</summary> | ||
436 | Retry = 4, | ||
437 | |||
438 | /// <summary>IDIGNORE</summary> | ||
439 | Ignore = 5, | ||
440 | |||
441 | /// <summary>IDYES</summary> | ||
442 | Yes = 6, | ||
443 | |||
444 | /// <summary>IDNO</summary> | ||
445 | No = 7, | ||
446 | } | ||
447 | |||
448 | /// <summary> | ||
449 | /// Specifies constants defining which buttons to display for a message. This can be cast to | ||
450 | /// the MessageBoxButtons enum in System.Windows.Forms and System.Windows. | ||
451 | /// </summary> | ||
452 | public enum MessageButtons | ||
453 | { | ||
454 | /// <summary> | ||
455 | /// The message contains an OK button. | ||
456 | /// </summary> | ||
457 | OK = 0, | ||
458 | |||
459 | /// <summary> | ||
460 | /// The message contains OK and Cancel buttons. | ||
461 | /// </summary> | ||
462 | OKCancel = 1, | ||
463 | |||
464 | /// <summary> | ||
465 | /// The message contains Abort, Retry, and Ignore buttons. | ||
466 | /// </summary> | ||
467 | AbortRetryIgnore = 2, | ||
468 | |||
469 | /// <summary> | ||
470 | /// The message contains Yes, No, and Cancel buttons. | ||
471 | /// </summary> | ||
472 | YesNoCancel = 3, | ||
473 | |||
474 | /// <summary> | ||
475 | /// The message contains Yes and No buttons. | ||
476 | /// </summary> | ||
477 | YesNo = 4, | ||
478 | |||
479 | /// <summary> | ||
480 | /// The message contains Retry and Cancel buttons. | ||
481 | /// </summary> | ||
482 | RetryCancel = 5, | ||
483 | } | ||
484 | |||
485 | /// <summary> | ||
486 | /// Specifies constants defining which information to display. This can be cast to | ||
487 | /// the MessageBoxIcon enum in System.Windows.Forms and System.Windows. | ||
488 | /// </summary> | ||
489 | public enum MessageIcon | ||
490 | { | ||
491 | /// <summary> | ||
492 | /// The message contain no symbols. | ||
493 | /// </summary> | ||
494 | None = 0, | ||
495 | |||
496 | /// <summary> | ||
497 | /// The message contains a symbol consisting of white X in a circle with a red background. | ||
498 | /// </summary> | ||
499 | Error = 16, | ||
500 | |||
501 | /// <summary> | ||
502 | /// The message contains a symbol consisting of a white X in a circle with a red background. | ||
503 | /// </summary> | ||
504 | Hand = 16, | ||
505 | |||
506 | /// <summary> | ||
507 | /// The message contains a symbol consisting of white X in a circle with a red background. | ||
508 | /// </summary> | ||
509 | Stop = 16, | ||
510 | |||
511 | /// <summary> | ||
512 | /// The message contains a symbol consisting of a question mark in a circle. | ||
513 | /// </summary> | ||
514 | Question = 32, | ||
515 | |||
516 | /// <summary> | ||
517 | /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background. | ||
518 | /// </summary> | ||
519 | Exclamation = 48, | ||
520 | |||
521 | /// <summary> | ||
522 | /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background. | ||
523 | /// </summary> | ||
524 | Warning = 48, | ||
525 | |||
526 | /// <summary> | ||
527 | /// The message contains a symbol consisting of a lowercase letter i in a circle. | ||
528 | /// </summary> | ||
529 | Information = 64, | ||
530 | |||
531 | /// <summary> | ||
532 | /// The message contains a symbol consisting of a lowercase letter i in a circle. | ||
533 | /// </summary> | ||
534 | Asterisk = 64, | ||
535 | } | ||
536 | |||
537 | /// <summary> | ||
538 | /// Specifies constants defining the default button on a message. This can be cast to | ||
539 | /// the MessageBoxDefaultButton enum in System.Windows.Forms and System.Windows. | ||
540 | /// </summary> | ||
541 | public enum MessageDefaultButton | ||
542 | { | ||
543 | /// <summary> | ||
544 | /// The first button on the message is the default button. | ||
545 | /// </summary> | ||
546 | Button1 = 0, | ||
547 | |||
548 | /// <summary> | ||
549 | /// The second button on the message is the default button. | ||
550 | /// </summary> | ||
551 | Button2 = 256, | ||
552 | |||
553 | /// <summary> | ||
554 | /// The third button on the message is the default button. | ||
555 | /// </summary> | ||
556 | Button3 = 512, | ||
557 | } | ||
558 | |||
559 | /// <summary> | ||
560 | /// Additional styles for use with message boxes. | ||
561 | /// </summary> | ||
562 | [Flags] | ||
563 | internal enum MessageBoxStyles | ||
564 | { | ||
565 | /// <summary> | ||
566 | /// The message box is created with the WS_EX_TOPMOST window style. | ||
567 | /// </summary> | ||
568 | TopMost = 0x00040000, | ||
569 | |||
570 | /// <summary> | ||
571 | /// The caller is a service notifying the user of an event. | ||
572 | /// The function displays a message box on the current active desktop, even if there is no user logged on to the computer. | ||
573 | /// </summary> | ||
574 | ServiceNotification = 0x00200000, | ||
575 | } | ||
576 | |||
577 | /// <summary> | ||
578 | /// Specifies the different patch states for <see cref="PatchInstallation.GetPatches(string, string, string, UserContexts, PatchStates)"/>. | ||
579 | /// </summary> | ||
580 | [Flags] | ||
581 | public enum PatchStates : int | ||
582 | { | ||
583 | /// <summary>Invalid value.</summary> | ||
584 | None = 0, | ||
585 | |||
586 | /// <summary>Patches applied to a product.</summary> | ||
587 | Applied = 1, | ||
588 | |||
589 | /// <summary>Patches that are superseded by other patches.</summary> | ||
590 | Superseded = 2, | ||
591 | |||
592 | /// <summary>Patches that are obsolesced by other patches.</summary> | ||
593 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")] | ||
594 | Obsoleted = 4, | ||
595 | |||
596 | /// <summary>Patches that are registered to a product but not applied.</summary> | ||
597 | Registered = 8, | ||
598 | |||
599 | /// <summary>All valid patch states.</summary> | ||
600 | All = (Applied | Superseded | Obsoleted | Registered) | ||
601 | } | ||
602 | |||
603 | /// <summary> | ||
604 | /// Specifies the reinstall mode for <see cref="Installer.ReinstallFeature"/> or <see cref="Installer.ReinstallProduct"/>. | ||
605 | /// </summary> | ||
606 | [Flags] | ||
607 | public enum ReinstallModes : int | ||
608 | { | ||
609 | /// <summary>Reinstall only if file is missing.</summary> | ||
610 | FileMissing = 0x00000002, | ||
611 | |||
612 | /// <summary>Reinstall if file is missing, or older version.</summary> | ||
613 | FileOlderVersion = 0x00000004, | ||
614 | |||
615 | /// <summary>Reinstall if file is missing, or equal or older version.</summary> | ||
616 | FileEqualVersion = 0x00000008, | ||
617 | |||
618 | /// <summary>Reinstall if file is missing, or not exact version.</summary> | ||
619 | FileExact = 0x00000010, | ||
620 | |||
621 | /// <summary>Checksum executables, reinstall if missing or corrupt.</summary> | ||
622 | FileVerify = 0x00000020, | ||
623 | |||
624 | /// <summary>Reinstall all files, regardless of version.</summary> | ||
625 | FileReplace = 0x00000040, | ||
626 | |||
627 | /// <summary>Insure required machine reg entries.</summary> | ||
628 | MachineData = 0x00000080, | ||
629 | |||
630 | /// <summary>Insure required user reg entries.</summary> | ||
631 | UserData = 0x00000100, | ||
632 | |||
633 | /// <summary>Validate shortcuts items.</summary> | ||
634 | Shortcut = 0x00000200, | ||
635 | |||
636 | /// <summary>Use re-cache source install package.</summary> | ||
637 | Package = 0x00000400, | ||
638 | } | ||
639 | |||
640 | /// <summary> | ||
641 | /// Attributes for <see cref="Transaction"/> methods. | ||
642 | /// </summary> | ||
643 | [Flags] | ||
644 | public enum TransactionAttributes : int | ||
645 | { | ||
646 | /// <summary>No attributes.</summary> | ||
647 | None = 0x00000000, | ||
648 | |||
649 | /// <summary>Request that the Windows Installer not shutdown the embedded UI until the transaction is complete.</summary> | ||
650 | ChainEmbeddedUI = 0x00000001, | ||
651 | |||
652 | /// <summary>Request that the Windows Installer transfer the embedded UI from the original installation.</summary> | ||
653 | JoinExistingEmbeddedUI = 0x00000002, | ||
654 | } | ||
655 | |||
656 | /// <summary> | ||
657 | /// Transform error conditions available for <see cref="Database.CreateTransformSummaryInfo"/> or | ||
658 | /// <see cref="Database.ApplyTransform(string,TransformErrors)"/>. | ||
659 | /// </summary> | ||
660 | [Flags] | ||
661 | public enum TransformErrors : int | ||
662 | { | ||
663 | /// <summary>No error conditions.</summary> | ||
664 | None = 0x0000, | ||
665 | |||
666 | /// <summary>Adding a row that already exists.</summary> | ||
667 | AddExistingRow = 0x0001, | ||
668 | |||
669 | /// <summary>Deleting a row that doesn't exist.</summary> | ||
670 | DelMissingRow = 0x0002, | ||
671 | |||
672 | /// <summary>Adding a table that already exists.</summary> | ||
673 | AddExistingTable = 0x0004, | ||
674 | |||
675 | /// <summary>Deleting a table that doesn't exist.</summary> | ||
676 | DelMissingTable = 0x0008, | ||
677 | |||
678 | /// <summary>Updating a row that doesn't exist.</summary> | ||
679 | UpdateMissingRow = 0x0010, | ||
680 | |||
681 | /// <summary>Transform and database code pages do not match and neither code page is neutral.</summary> | ||
682 | ChangeCodePage = 0x0020, | ||
683 | |||
684 | /// <summary>Create the temporary _TransformView table when applying the transform.</summary> | ||
685 | ViewTransform = 0x0100, | ||
686 | } | ||
687 | |||
688 | /// <summary> | ||
689 | /// Transform validation flags available for <see cref="Database.CreateTransformSummaryInfo"/>. | ||
690 | /// </summary> | ||
691 | [Flags] | ||
692 | public enum TransformValidations : int | ||
693 | { | ||
694 | /// <summary>Validate no properties.</summary> | ||
695 | None = 0x0000, | ||
696 | |||
697 | /// <summary>Default language must match base database.</summary> | ||
698 | Language = 0x0001, | ||
699 | |||
700 | /// <summary>Product must match base database.</summary> | ||
701 | Product = 0x0002, | ||
702 | |||
703 | /// <summary>Check major version only.</summary> | ||
704 | MajorVersion = 0x0008, | ||
705 | |||
706 | /// <summary>Check major and minor versions only.</summary> | ||
707 | MinorVersion = 0x0010, | ||
708 | |||
709 | /// <summary>Check major, minor, and update versions.</summary> | ||
710 | UpdateVersion = 0x0020, | ||
711 | |||
712 | /// <summary>Installed version < base version.</summary> | ||
713 | NewLessBaseVersion = 0x0040, | ||
714 | |||
715 | /// <summary>Installed version <= base version.</summary> | ||
716 | NewLessEqualBaseVersion = 0x0080, | ||
717 | |||
718 | /// <summary>Installed version = base version.</summary> | ||
719 | NewEqualBaseVersion = 0x0100, | ||
720 | |||
721 | /// <summary>Installed version >= base version.</summary> | ||
722 | NewGreaterEqualBaseVersion = 0x0200, | ||
723 | |||
724 | /// <summary>Installed version > base version.</summary> | ||
725 | NewGreaterBaseVersion = 0x0400, | ||
726 | |||
727 | /// <summary>UpgradeCode must match base database.</summary> | ||
728 | UpgradeCode = 0x0800, | ||
729 | } | ||
730 | |||
731 | /// <summary> | ||
732 | /// Specifies the installation context for <see cref="ProductInstallation"/>s, | ||
733 | /// <see cref="PatchInstallation"/>es, and | ||
734 | /// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/> | ||
735 | /// </summary> | ||
736 | [Flags] | ||
737 | public enum UserContexts : int | ||
738 | { | ||
739 | /// <summary>Not installed.</summary> | ||
740 | None = 0, | ||
741 | |||
742 | /// <summary>User managed install context.</summary> | ||
743 | UserManaged = 1, | ||
744 | |||
745 | /// <summary>User non-managed context.</summary> | ||
746 | UserUnmanaged = 2, | ||
747 | |||
748 | /// <summary>Per-machine context.</summary> | ||
749 | Machine = 4, | ||
750 | |||
751 | /// <summary>All contexts, or all valid values.</summary> | ||
752 | All = (UserManaged | UserUnmanaged | Machine), | ||
753 | |||
754 | /// <summary>All user-managed contexts.</summary> | ||
755 | AllUserManaged = 8, | ||
756 | } | ||
757 | |||
758 | /// <summary> | ||
759 | /// Defines the type of error encountered by the <see cref="View.Validate"/>, <see cref="View.ValidateNew"/>, | ||
760 | /// or <see cref="View.ValidateFields"/> methods of the <see cref="View"/> class. | ||
761 | /// </summary> | ||
762 | public enum ValidationError : int | ||
763 | { | ||
764 | /* | ||
765 | InvalidArg = -3, | ||
766 | MoreData = -2, | ||
767 | FunctionError = -1, | ||
768 | */ | ||
769 | |||
770 | /// <summary>No error.</summary> | ||
771 | None = 0, | ||
772 | |||
773 | /// <summary>The new record duplicates primary keys of the existing record in a table.</summary> | ||
774 | DuplicateKey = 1, | ||
775 | |||
776 | /// <summary>There are no null values allowed, or the column is about to be deleted but is referenced by another row.</summary> | ||
777 | Required = 2, | ||
778 | |||
779 | /// <summary>The corresponding record in a foreign table was not found.</summary> | ||
780 | BadLink = 3, | ||
781 | |||
782 | /// <summary>The data is greater than the maximum value allowed.</summary> | ||
783 | Overflow = 4, | ||
784 | |||
785 | /// <summary>The data is less than the minimum value allowed.</summary> | ||
786 | Underflow = 5, | ||
787 | |||
788 | /// <summary>The data is not a member of the values permitted in the set.</summary> | ||
789 | NotInSet = 6, | ||
790 | |||
791 | /// <summary>An invalid version string was supplied.</summary> | ||
792 | BadVersion = 7, | ||
793 | |||
794 | /// <summary>The case was invalid. The case must be all uppercase or all lowercase.</summary> | ||
795 | BadCase = 8, | ||
796 | |||
797 | /// <summary>An invalid GUID was supplied.</summary> | ||
798 | BadGuid = 9, | ||
799 | |||
800 | /// <summary>An invalid wildcard file name was supplied, or the use of wildcards was invalid.</summary> | ||
801 | BadWildcard = 10, | ||
802 | |||
803 | /// <summary>An invalid identifier was supplied.</summary> | ||
804 | BadIdentifier = 11, | ||
805 | |||
806 | /// <summary>Invalid language IDs were supplied.</summary> | ||
807 | BadLanguage = 12, | ||
808 | |||
809 | /// <summary>An invalid file name was supplied.</summary> | ||
810 | BadFileName = 13, | ||
811 | |||
812 | /// <summary>An invalid path was supplied.</summary> | ||
813 | BadPath = 14, | ||
814 | |||
815 | /// <summary>An invalid conditional statement was supplied.</summary> | ||
816 | BadCondition = 15, | ||
817 | |||
818 | /// <summary>An invalid format string was supplied.</summary> | ||
819 | BadFormatted = 16, | ||
820 | |||
821 | /// <summary>An invalid template string was supplied.</summary> | ||
822 | BadTemplate = 17, | ||
823 | |||
824 | /// <summary>An invalid string was supplied in the DefaultDir column of the Directory table.</summary> | ||
825 | BadDefaultDir = 18, | ||
826 | |||
827 | /// <summary>An invalid registry path string was supplied.</summary> | ||
828 | BadRegPath = 19, | ||
829 | |||
830 | /// <summary>An invalid string was supplied in the CustomSource column of the CustomAction table.</summary> | ||
831 | BadCustomSource = 20, | ||
832 | |||
833 | /// <summary>An invalid property string was supplied.</summary> | ||
834 | BadProperty = 21, | ||
835 | |||
836 | /// <summary>The _Validation table is missing a reference to a column.</summary> | ||
837 | MissingData = 22, | ||
838 | |||
839 | /// <summary>The category column of the _Validation table for the column is invalid.</summary> | ||
840 | BadCategory = 23, | ||
841 | |||
842 | /// <summary>The table in the Keytable column of the _Validation table was not found or loaded.</summary> | ||
843 | BadKeyTable = 24, | ||
844 | |||
845 | /// <summary>The value in the MaxValue column of the _Validation table is less than the value in the MinValue column.</summary> | ||
846 | BadMaxMinValues = 25, | ||
847 | |||
848 | /// <summary>An invalid cabinet name was supplied.</summary> | ||
849 | BadCabinet = 26, | ||
850 | |||
851 | /// <summary>An invalid shortcut target name was supplied.</summary> | ||
852 | BadShortcut = 27, | ||
853 | |||
854 | /// <summary>The string is too long for the length specified by the column definition.</summary> | ||
855 | StringOverflow = 28, | ||
856 | |||
857 | /// <summary>An invalid localization attribute was supplied. (Primary keys cannot be localized.)</summary> | ||
858 | BadLocalizeAttrib = 29 | ||
859 | } | ||
860 | |||
861 | /// <summary> | ||
862 | /// Specifies the modify mode for <see cref="View.Modify"/>. | ||
863 | /// </summary> | ||
864 | public enum ViewModifyMode : int | ||
865 | { | ||
866 | /// <summary> | ||
867 | /// Refreshes the information in the supplied record without changing the position | ||
868 | /// in the result set and without affecting subsequent fetch operations. | ||
869 | /// </summary> | ||
870 | Seek = -1, | ||
871 | |||
872 | /// <summary>Refreshes the data in a Record.</summary> | ||
873 | Refresh = 0, | ||
874 | |||
875 | /// <summary>Inserts a Record into the view.</summary> | ||
876 | Insert = 1, | ||
877 | |||
878 | /// <summary>Updates the View with new data from the Record.</summary> | ||
879 | Update = 2, | ||
880 | |||
881 | /// <summary>Updates or inserts a Record into the View.</summary> | ||
882 | Assign = 3, | ||
883 | |||
884 | /// <summary>Updates or deletes and inserts a Record into the View.</summary> | ||
885 | Replace = 4, | ||
886 | |||
887 | /// <summary>Inserts or validates a record.</summary> | ||
888 | Merge = 5, | ||
889 | |||
890 | /// <summary>Deletes a Record from the View.</summary> | ||
891 | Delete = 6, | ||
892 | |||
893 | /// <summary>Inserts a Record into the View. The inserted data is not persistent.</summary> | ||
894 | InsertTemporary = 7, | ||
895 | |||
896 | /// <summary>Validates a record.</summary> | ||
897 | Validate = 8, | ||
898 | |||
899 | /// <summary>Validates a new record.</summary> | ||
900 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] | ||
901 | ValidateNew = 9, | ||
902 | |||
903 | /// <summary>Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record.</summary> | ||
904 | ValidateField = 10, | ||
905 | |||
906 | /// <summary>Validates a record that will be deleted later.</summary> | ||
907 | ValidateDelete = 11, | ||
908 | } | ||
909 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources new file mode 100644 index 00000000..5564e88a --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources | |||
Binary files differ | |||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt new file mode 100644 index 00000000..ec7c97b1 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt | |||
@@ -0,0 +1,404 @@ | |||
1 | ; Windows Installer Internal Error Messages | ||
2 | ; | ||
3 | ; Error numbers greater than 2000 are "internal errors" and do not have | ||
4 | ; localized strings in msimsg.dll. The messages are provided here | ||
5 | ; (in English only) because they can be informative and time-saving | ||
6 | ; during developement. | ||
7 | ; | ||
8 | |||
9 | 2101=Shortcuts not supported by the OS. | ||
10 | 2102=Invalid .INI action: [2] | ||
11 | 2103=Could not resolve path for shell folder [2]. | ||
12 | 2104=Writing .INI file: [3]: System error: [2] | ||
13 | 2105=Shortcut Creation [3] Failed. System error: [2] | ||
14 | 2106=Shortcut Deletion [3] Failed. System error: [2] | ||
15 | 2107=Error [3] registering type library [2]. | ||
16 | 2108=Error [3] unregistering type library [2]. | ||
17 | 2109=Section missing for INI action. | ||
18 | 2110=Key missing for INI action. | ||
19 | 2111=Detection of running apps failed, could not get performance data. Reg operation returned : [2]. | ||
20 | 2112=Detection of running apps failed, could not get performance index. Reg operation returned : [2]. | ||
21 | 2113=Detection of running apps failed. | ||
22 | 2200=Database: [2]. Database object creation failed, mode = [3]. | ||
23 | 2201=Database: [2]. Initialization failed, out of memory. | ||
24 | 2202=Database: [2]. Data access failed, out of memory. | ||
25 | 2203=Database: [2]. Cannot open database file. System error [3]. | ||
26 | 2204=Database: [2]. Table already exists: [3] | ||
27 | 2205=Database: [2]. Table does not exist: [3] | ||
28 | 2206=Database: [2]. Table could not be dropped: [3] | ||
29 | 2207=Database: [2]. Intent violation. | ||
30 | 2208=Database: [2]. Insufficient parameters for Execute. | ||
31 | 2209=Database: [2]. Cursor in invalid state. | ||
32 | 2210=Database: [2]. Invalid update data type in column [3] | ||
33 | 2211=Database: [2]. Could not create database table [3] | ||
34 | 2212=Database: [2]. Database not in writable state. | ||
35 | 2213=Database: [2]. Error saving database tables. | ||
36 | 2214=Database: [2]. Error writing export file: [3] | ||
37 | 2215=Database: [2]. Cannot open import file: [3] | ||
38 | 2216=Database: [2]. Import file format error: [3], Line [4] | ||
39 | 2217=Database: [2]. Wrong state to CreateOutputDatabase [3]. | ||
40 | 2218=Database: [2]. Table name not supplied. | ||
41 | 2219=Database: [2]. Invalid Installer database format. | ||
42 | 2220=Database: [2]. Invalid row/field data. | ||
43 | 2221=Database: [2]. Code page conflict in import file: [3]. | ||
44 | 2222=Database: [2]. Transform or merge code page [3] differs from database code page [4]. | ||
45 | 2223=Database: [2]. Databases are the same. No transform generated. | ||
46 | 2224=Database: [2]. GenerateTransform: Database corrupt. Table: [3] | ||
47 | 2225=Database: [2]. Transform: Cannot transform a temporary table. Table: [3] | ||
48 | 2226=Database: [2]. Transform failed. | ||
49 | 2227=Database: [2]. Invalid identifier '[3]' in SQL query: [4] | ||
50 | 2228=Database: [2]. Unknown table '[3]' in SQL query: [4] | ||
51 | 2229=Database: [2]. Could not load table '[3]' in SQL query: [4] | ||
52 | 2230=Database: [2]. Repeated table '[3]' in SQL query: [4] | ||
53 | 2231=Database: [2]. Missing ')' in SQL query: [3] | ||
54 | 2232=Database: [2]. Unexpected token '[3]' in SQL query: [4] | ||
55 | 2233=Database: [2]. No columns in SELECT clause in SQL query: [3] | ||
56 | 2234=Database: [2]. No columns in ORDER BY clause in SQL query: [3] | ||
57 | 2235=Database: [2]. Column '[3]' not present or ambiguous in SQL query: [4] | ||
58 | 2236=Database: [2]. Invalid operator '[3]' in SQL query: [4] | ||
59 | 2237=Database: [2]. Invalid or missing query string: [3] | ||
60 | 2238=Database: [2]. Missing FROM clause in SQL query: [3] | ||
61 | 2239=Database: [2]. Insufficient values in INSERT SQL stmt. | ||
62 | 2240=Database: [2]. Missing update columns in UPDATE SQL stmt. | ||
63 | 2241=Database: [2]. Missing insert columns in INSERT SQL stmt. | ||
64 | 2242=Database: [2]. Column '[3]' repeated | ||
65 | 2243=Database: [2]. No primary columns defined for table creation. | ||
66 | 2244=Database: [2]. Invalid type specifier '[3]' in SQL query [4]. | ||
67 | 2245=IStorage::Stat failed with error [3] | ||
68 | 2246=Database: [2]. Invalid Installer transform format. | ||
69 | 2247=Database: [2] Transform stream read/write failure. | ||
70 | 2248=Database: [2] GenerateTransform/Merge: Column type in base table doesn't match reference table. Table: [3] Col #: [4] | ||
71 | 2249=Database: [2] GenerateTransform: More columns in base table than in reference table. Table: [3] | ||
72 | 2250=Database: [2] Transform: Cannot add existing row. Table: [3] | ||
73 | 2251=Database: [2] Transform: Cannot delete row that doesn't exist. Table: [3] | ||
74 | 2252=Database: [2] Transform: Cannot add existing table. Table: [3] | ||
75 | 2253=Database: [2] Transform: Cannot delete table that doesn't exist. Table: [3] | ||
76 | 2254=Database: [2] Transform: Cannot update row that doesn't exist. Table: [3] | ||
77 | 2255=Database: [2] Transform: Column with this name already exists. Table: [3] Col: [4] | ||
78 | 2256=Database: [2] GenerateTransform/Merge: Number of primary keys in base table doesn't match reference table. Table: [3] | ||
79 | 2257=Database: [2]. Intent to modify read only table: [3] | ||
80 | 2258=Database: [2]. Type mismatch in parameter: [3] | ||
81 | 2259=Database: [2] Table(s) Update failed. | ||
82 | 2260=Storage CopyTo failed. System error: [3] | ||
83 | 2261=Could not remove stream [2]. System error: [3] | ||
84 | 2262=Stream does not exist: [2]. System error: [3] | ||
85 | 2263=Could not open stream [2]. System error: [3] | ||
86 | 2264=Could not remove stream [2]. System error: [3] | ||
87 | 2265=Could not commit storage. System error: [3] | ||
88 | 2266=Could not rollback storage. System error: [3] | ||
89 | 2267=Could not delete storage [2]. System error: [3] | ||
90 | 2268=Database: [2]. Merge: There were merge conflicts reported in [3] tables. | ||
91 | 2269=Database: [2]. Merge: The column count differed in the '[3]' table of the two databases. | ||
92 | 2270=Database: [2]. GenerateTransform/Merge: Column name in base table doesn't match reference table. Table: [3] Col #: [4] | ||
93 | 2271=SummaryInformation write for transform failed. | ||
94 | 2272=Database: [2]. MergeDatabase will not write any changes because the database is open read-only. | ||
95 | 2273=Database: [2]. MergeDatabase: A reference to the base database was passed as the reference database. | ||
96 | 2274=Database: [2]. MergeDatabase: Unable to write errors to Error table. Could be due to a non-nullable column in a predefined Error table. | ||
97 | 2275=Database: [2]. Specified Modify [3] operation invalid for table joins. | ||
98 | 2276=Database: [2]. Code page [3] not supported by the system. | ||
99 | 2277=Database: [2]. Failed to save table [3]. | ||
100 | 2278=Database: [2]. Exceeded number of expressions limit of 32 in WHERE clause of SQL query: [3]. | ||
101 | 2279=Database: [2] Transform: Too many columns in base table [3] | ||
102 | 2280=Database: [2]. Could not create column [3] for table [4] | ||
103 | 2281=Could not rename stream [2]. System error: [3] | ||
104 | 2282=Stream name invalid [2]. | ||
105 | 2302=Patch notify: [2] bytes patched to far. | ||
106 | 2303=Error getting volume info. GetLastError: [2] | ||
107 | 2304=Error getting disk free space. GetLastError: [2]. Volume: [3] | ||
108 | 2305=Error waiting for patch thread. GetLastError: [2]. | ||
109 | 2306=Could not create thread for patch application. GetLastError: [2]. | ||
110 | 2307=Source file key name is null. | ||
111 | 2308=Destination File Name is Null | ||
112 | 2309=Attempting to patch file [2] when patch already in progress. | ||
113 | 2310=Attempting to continue patch when no patch is in progress. | ||
114 | 2315=Missing Path Separator: [2] | ||
115 | 2318=File does not exist: [2] | ||
116 | 2319=Error setting file attribute: [3] GetLastError: [2] | ||
117 | 2320=File not writable: [2] | ||
118 | 2321=Error creating file: [2] | ||
119 | 2322=User canceled | ||
120 | 2323=Invalid File Attribute | ||
121 | 2324=Could not open file: [3] GetLastError: [2] | ||
122 | 2325=Could not get file time for file: [3] GetLastError: [2] | ||
123 | 2326=Error in FileToDosDateTime. | ||
124 | 2327=Could not remove directory: [3] GetLastError: [2] | ||
125 | 2328=Error getting file version info for file: [2] | ||
126 | 2329=Error deleting file: [3]. GetLastError: [2] | ||
127 | 2330=Error getting file attributes: [3]. GetLastError: [2] | ||
128 | 2331=Error loading library [2] or finding entry point [3] | ||
129 | 2332=Error getting file attributes. GetLastError: [2] | ||
130 | 2333=Error setting file attributes. GetLastError: [2] | ||
131 | 2334=Error converting file time to local time for file: [3]. GetLastError: [2] | ||
132 | 2335=Path: [2] is not a parent of [3] | ||
133 | 2336=Error creating temp file on path: [3]. GetLastError: [2] | ||
134 | 2337=Could not close file: [3] GetLastError: [2] | ||
135 | 2338=Could not update resource for file: [3] GetLastError: [2] | ||
136 | 2339=Could not set file time for file: [3] GetLastError: [2] | ||
137 | 2340=Could not update resource for file: [3], Missing Resource | ||
138 | 2341=Could not update resource for file: [3], Resource too large | ||
139 | 2342=Could not update resource for file: [3] GetLastError: [2] | ||
140 | 2343=Specified path is empty. | ||
141 | 2344=Could not find required file IMAGEHLP.DLL to validate file:[2] | ||
142 | 2345=[2]: File does not contain a valid checksum value. | ||
143 | 2347=User ignore | ||
144 | 2348=Error attempting to read from cabinet stream. | ||
145 | 2349=Copy Resumed With Different Info | ||
146 | 2350=FDI Server Error | ||
147 | 2351=File key '[2]' not found in cabinet '[3]'. The installation cannot continue. | ||
148 | 2352=Couldn't initialize cabinet file server. The required file 'Cabinet.dll' may be missing. | ||
149 | 2353=Not a cabinet | ||
150 | 2354=Cannot handle cabinet | ||
151 | 2355=Corrupt cabinet | ||
152 | 2356=Couldn't locate cabinet in stream: [2]. | ||
153 | 2357=Cannot set attributes | ||
154 | 2358=Error determining whether file is in-use: [3]. GetLastError: [2] | ||
155 | 2359=Unable to create the target file - file may be in use. | ||
156 | 2360=progress tick. | ||
157 | 2361=Need next cabinet. | ||
158 | 2362=Folder not found: [2] | ||
159 | 2363=Could not enumerate subfolders for folder: [2] | ||
160 | 2364=Bad enumeration constant in CreateCopier call. | ||
161 | 2365=Could not BindImage exe file [2] | ||
162 | 2366=User Failure | ||
163 | 2367=User Abort. | ||
164 | 2368=Failed to get network resource information. Error [2], network path [3]. Extended error: network provider [5], error code [4], error description [6]. | ||
165 | 2370=Invalid CRC checksum value for [2] file.{ Its header says [3] for checksum, its computed value is [4].} | ||
166 | 2371=Could not apply patch to file [2]. GetLastError: [3] | ||
167 | 2372=Patch file [2] is corrupt or of an invalid format. Attempting to patch file [3]. GetLastError: [4] | ||
168 | 2373=File [2] is not a valid patch file. | ||
169 | 2374=File [2] is not a valid destination file for patch file [3]. | ||
170 | 2375=Unknown patching error: [2]. | ||
171 | 2376=Cabinet not found. | ||
172 | 2379=Error opening file for read: [3] GetLastError: [2] | ||
173 | 2380=Error opening file for write: [3] GetLastError: [2] | ||
174 | 2381=Directory does not exist: [2] | ||
175 | 2382=Drive not ready: [2] | ||
176 | 2401=64-bit registry operation attempted on 32-bit operating system for key [2]. | ||
177 | 2402=Out of memory. | ||
178 | 2501=Could not create rollback script enumerator | ||
179 | 2502=Called InstallFinalize when no install in progress. | ||
180 | 2503=Called RunScript when not marked in progress. | ||
181 | 2601=Invalid value for property [2]: '[3]' | ||
182 | 2602=The [2] table entry '[3]' has no associated entry in the Media table. | ||
183 | 2603=Duplicate Table Name [2] | ||
184 | 2604=[2] property undefined. | ||
185 | 2605=Could not find server [2] in [3] or [4]. | ||
186 | 2606=Value of property [2] is not a valid full path: '[3]'. | ||
187 | 2607=Media table not found or empty (required for installation of files). | ||
188 | 2608=Could not create security descriptor for object. Error: '[2]'. | ||
189 | 2609=Attempt to migrate product settings before initialization. | ||
190 | 2611=The file [2] is marked as compressed, but the associated media entry does not specify a cabinet. | ||
191 | 2612=Stream not found in '[2]' column. Primary key: '[3]'. | ||
192 | 2613=RemoveExistingProducts action sequenced incorrectly. | ||
193 | 2614=Could not access IStorage object from installation package. | ||
194 | 2615=Skipped unregistration of Module [2] due to source resolution failure. | ||
195 | 2616=Companion file [2] parent missing. | ||
196 | 2617=Shared component [2] not found in Component table. | ||
197 | 2618=Isolated application component [2] not found in Component table. | ||
198 | 2619=Isolated components [2], [3] not part of same feature. | ||
199 | 2620=Key file of isolated application component [2] not in File table. | ||
200 | 2621=Resource DLL or Resource ID information for shortcut [2] set incorrectly. | ||
201 | 2701=The Component Table exceeds the acceptable tree depth of [2] levels. | ||
202 | 2702=A Feature Table record ([2]) references a non-existent parent in the Attributes field. | ||
203 | 2703=Property name for root source path not defined: [2] | ||
204 | 2704=Root directory property undefined: [2] | ||
205 | 2705=Invalid table: [2]; Could not be linked as tree. | ||
206 | 2706=Source paths not created. No path exists for entry [2] in Directory Table | ||
207 | 2707=Target paths not created. No path exists for entry [2] in Directory Table | ||
208 | 2708=No entries found in the file table. | ||
209 | 2709=The specified Component name ('[2]') not found in Component Table. | ||
210 | 2710=The requested 'Select' state is illegal for this Component. | ||
211 | 2711=The specified Feature name ('[2]') not found in Feature Table. | ||
212 | 2712=Invalid return from modeless dialog: [3], in action [2]. | ||
213 | 2713=Null value in a non-nullable column ('[2]' in '[3]' column of the '[4]' table. | ||
214 | 2714=Invalid value for default folder name: [2]. | ||
215 | 2715=The specified File key ('[2]') not found in the File Table. | ||
216 | 2716=Couldn't create a random subcomponent name for component '[2]'. | ||
217 | 2717=Bad action condition or error calling custom action '[2]'. | ||
218 | 2718=Missing package name for product code '[2]'. | ||
219 | 2719=Neither UNC nor drive letter path found in source '[2]'. | ||
220 | 2720=Error opening source list key. Error: '[2]' | ||
221 | 2721=Custom action [2] not found in Binary table stream | ||
222 | 2722=Custom action [2] not found in File table | ||
223 | 2723=Custom action [2] specifies unsupported type | ||
224 | 2724=The volume label '[2]' on the media you're running from doesn't match the label '[3]' given in the Media table. This is allowed only if you have only 1 entry in your Media table. | ||
225 | 2725=Invalid database tables | ||
226 | 2726=Action not found: [2] | ||
227 | 2727=The directory entry '[2]' does not exist in the Directory table | ||
228 | 2728=Table definition error: [2] | ||
229 | 2729=Install engine not initialized. | ||
230 | 2730=Bad value in database. Table: '[2]'; Primary key: '[3]'; Column: '[4]' | ||
231 | 2731=Selection Manager not initialized. | ||
232 | 2732=Directory Manager not initialized. | ||
233 | 2733=Bad foreign key ('[2]') in '[3]' column of the '[4]' table. | ||
234 | 2734=Invalid Reinstall mode character. | ||
235 | 2735=Custom action '[2]' has caused an unhandled exception and has been stopped. This may be the result of an internal error in the custom action, such as an access violation. | ||
236 | 2736=Generation of custom action temp file failed: [2] | ||
237 | 2737=Could not access custom action [2], entry [3], library [4] | ||
238 | 2738=Could not access VBScript runtime for custom action [2] | ||
239 | 2739=Could not access JavaScript runtime for custom action [2] | ||
240 | 2740=Custom action [2] script error [3], [4]: [5] Line [6], Column [7], [8] | ||
241 | 2741=Configuration information for product [2] is corrupt. Invalid info: [2] | ||
242 | 2742=Marshaling to Server failed: [2] | ||
243 | 2743=Could not execute custom action [2], location: [3], command: [4] | ||
244 | 2744=EXE failed called by custom action [2], location: [3], command: [4] | ||
245 | 2745=Transform [2] invalid for package [3]. Expected language [4], found language [5]. | ||
246 | 2746=Transform [2] invalid for package [3]. Expected product [4], found product [5]. | ||
247 | 2747=Transform [2] invalid for package [3]. Expected product version < [4], found product version [5]. | ||
248 | 2748=Transform [2] invalid for package [3]. Expected product version <= [4], found product version [5]. | ||
249 | 2749=Transform [2] invalid for package [3]. Expected product version == [4], found product version [5]. | ||
250 | 2750=Transform [2] invalid for package [3]. Expected product version >= [4], found product version [5]. | ||
251 | 2751=Transform [2] invalid for package [3]. Expected product version > [4], found product version [5]. | ||
252 | 2752=Could not open transform [2] stored as child storage of package [4]. | ||
253 | 2753=The File '[2]' is not marked for installation. | ||
254 | 2754=The File '[2]' is not a valid patch file. | ||
255 | 2755=Server returned unexpected error [2] attempting to install package [3]. | ||
256 | 2756=The property '[2]' was used as a directory property in one or more tables, but no value was ever assigned. | ||
257 | 2757=Could not create summary info for transform [2]. | ||
258 | 2758=Transform [2] doesn't contain a MSI version. | ||
259 | 2759=Transform [2] version [3] incompatible with engine; Min: [4], Max: [5]. | ||
260 | 2760=Transform [2] invalid for package [3]. Expected upgrade code [4], found [5]. | ||
261 | 2761=Cannot begin transaction. Global mutex not properly initialized. | ||
262 | 2762=Cannot write script record. Transaction not started. | ||
263 | 2763=Cannot run script. Transaction not started. | ||
264 | 2765=Assembly name missing from AssemblyName table : Component: [4]. | ||
265 | 2766=The file [2] is an invalid MSI storage file. | ||
266 | 2767=No more data{ while enumerating [2]}. | ||
267 | 2768=Transform in patch package is invalid. | ||
268 | 2769=Custom Action [2] did not close [3] handles. | ||
269 | 2770=Cached folder [2] not defined in internal cache folder table. | ||
270 | 2771=Upgrade of feature [2] has a missing component. | ||
271 | 2772=New upgrade feature [2] must be a leaf feature. | ||
272 | 2801=Unknown Message -- Type [2]. No action is taken. | ||
273 | 2802=No publisher is found for the event [2]. | ||
274 | 2803=Dialog View did not find a record for the dialog [2]. | ||
275 | 2804=On activation of the control [3] on dialog [2], failed to evaluate the condition [3]. | ||
276 | 2805= | ||
277 | 2806=The dialog [2] failed to evaluate the condition [3]. | ||
278 | 2807=The action [2] is not recognized. | ||
279 | 2808=Default button is ill-defined on dialog [2]. | ||
280 | 2809=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from [3] to [4], but there is no further pointer. | ||
281 | 2810=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from both [3] and [5] to [4]. | ||
282 | 2811=On dialog [2] control [3] has to take focus, but it is unable to do so. | ||
283 | 2812=The event [2] is not recognized. | ||
284 | 2813=The EndDialog event was called with the argument [2], but the dialog has a parent. | ||
285 | 2814=On the dialog [2] the control [3] names a non-existent control [4] as the next control. | ||
286 | 2815=ControlCondition table has a row without condition for the dialog [2]. | ||
287 | 2816=The EventMapping table refers to an invalid control [4] on dialog [2] for the event [3]. | ||
288 | 2817=The event [2] failed to set the attribute for the control [4] on dialog [3]. | ||
289 | 2818=In the ControlEvent table EndDialog has an unrecognized argument [2]. | ||
290 | 2819=Control [3] on dialog [2] needs a property linked to it. | ||
291 | 2820=Attempted to initialize an already initialized handler. | ||
292 | 2821=Attempted to initialize an already initialized dialog: [2]. | ||
293 | 2822=No other method can be called on dialog [2] until all the controls are added. | ||
294 | 2823=Attempted to initialize an already initialized control: [3] on dialog [2]. | ||
295 | 2824=The dialog attribute [3] needs a record of at least [2] field(s). | ||
296 | 2825=The control attribute [3] needs a record of at least [2] field(s). | ||
297 | 2826=Control [3] on dialog [2] extends beyond the boundaries of the dialog [4] by [5] pixels. | ||
298 | 2827=The button [4] on the radio button group [3] on dialog [2] extends beyond the boundaries of the group [5] by [6] pixels. | ||
299 | 2828=Tried to remove control [3] from dialog [2], but the control is not part of the dialog. | ||
300 | 2829=Attempt to use an uninitialized dialog. | ||
301 | 2830=Attempt to use an uninitialized control on dialog [2]. | ||
302 | 2831=The control [3] on dialog [2] does not support [5] the attribute [4]. | ||
303 | 2832=The dialog [2] does not support the attribute [3]. | ||
304 | 2833=Control [4] on dialog [3] ignored the message [2]. | ||
305 | 2834=The next pointers on the dialog [2] do not form a single loop. | ||
306 | 2835=The control [2] was not found on dialog [3]. | ||
307 | 2836=The control [3] on the dialog [2] cannot take focus. | ||
308 | 2837=The control [3] on dialog [2] wants the win proc to return [4]. | ||
309 | 2838=The item [2] in the selection table has itself as a parent. | ||
310 | 2839=Setting the property [2] failed. | ||
311 | 2840=Error dialog name mismatch. | ||
312 | 2841=No OK button was found on the error dialog | ||
313 | 2842=No text field was found on the error dialog. | ||
314 | 2843=The ErrorString attribute is not supported for standard dialogs. | ||
315 | 2844=Cannot execute an error dialog if the error string is not set. | ||
316 | 2845=The total width of the buttons exceeds the size of the error dialog. | ||
317 | 2846=SetFocus did not find the required control on the error dialog. | ||
318 | 2847=The control [3] on dialog [2] has both the icon and the bitmap style set. | ||
319 | 2848=Tried to set control [3] as the default button on dialog [2], but the control does not exist. | ||
320 | 2849=The control [3] on dialog [2] is of a type, that cannot be integer valued. | ||
321 | 2850=Unrecognized volume type. | ||
322 | 2851=The data for the icon [2] is not valid. | ||
323 | 2852=At least one control has to be added to dialog [2] before it is used. | ||
324 | 2853=Dialog [2] is a modeless dialog. The execute method should not be called on it. | ||
325 | 2854=On the dialog [2] the control [3] is designated as first active control, but there is no such control. | ||
326 | 2855=The radio button group [3] on dialog [2] has fewer than 2 buttons. | ||
327 | 2856=Creating a second copy of the dialog [2]. | ||
328 | 2857=The directory [2] is mentioned in the selection table but not found. | ||
329 | 2858=The data for the bitmap [2] is not valid. | ||
330 | 2859=Test error message. | ||
331 | 2860=Cancel button is ill-defined on dialog [2]. | ||
332 | 2861=The next pointers for the radio buttons on dialog [2] control [3] do not form a cycle. | ||
333 | 2862=The attributes for the control [3] on dialog [2] do not define a valid icon size. Setting the size to 16. | ||
334 | 2863=The control [3] on dialog [2] needs the icon [4] in size [5]x[5], but that size is not available. Loading the first available size. | ||
335 | 2864=The control [3] on dialog [2] received a browse event, but there is no configurable directory for the present selection. Likely cause: browse button is not authored correctly. | ||
336 | 2865=Control [3] on billboard [2] extends beyond the boundaries of the billboard [4] by [5] pixels. | ||
337 | 2866=The dialog [2] is not allowed to return the argument [3]. | ||
338 | 2867=The error dialog property is not set. | ||
339 | 2868=The error dialog [2] does not have the error style bit set. | ||
340 | 2869=The dialog [2] has the error style bit set, but is not an error dialog. | ||
341 | 2870=The help string [4] for control [3] on dialog [2] does not contain the separator character. | ||
342 | 2871=The [2] table is out of date: [3] | ||
343 | 2872=The argument of the CheckPath control event on dialog [2] is invalid. | ||
344 | 2873=On the dialog [2] the control [3] has an invalid string length limit: [4] | ||
345 | 2874=Changing the text font to [2] failed. | ||
346 | 2875=Changing the text color to [2] failed. | ||
347 | 2876=The control [3] on dialog [2] had to truncate the string: [4] | ||
348 | 2877=The binary data [2] was not found. | ||
349 | 2878=On the dialog [2] the control [3] has a possible value: [4]. This is an invalid or duplicate value. | ||
350 | 2879=The control [3] on dialog [2] cannot parse the mask string: [4] | ||
351 | 2880=Do not perform the remaining control events. | ||
352 | 2881=Initialization failed. | ||
353 | 2882=Dialog window class registration failed. | ||
354 | 2883=CreateNewDialog failed for the dialog [2]. | ||
355 | 2884=Failed to create a window for the dialog [2]! | ||
356 | 2885=Failed to create the control [3] on the dialog [2]. | ||
357 | 2886=Creating the [2] table failed. | ||
358 | 2887=Creating a cursor to the [2] table failed. | ||
359 | 2888=Executing the [2] view failed. | ||
360 | 2889=Creating the window for the control [3] on dialog [2] failed. | ||
361 | 2890=The handler failed in creating an initialized dialog. | ||
362 | 2891=Failed to destroy window for dialog [2]. | ||
363 | 2892=[2] is an integer only control, [3] is not a valid integer value. | ||
364 | 2893=The control [3] on dialog [2] can accept property values that are at most [5] characters long. The value [4] exceeds this limit, and has been truncated. | ||
365 | 2894=Loading RichEd20.dll failed. GetLastError() returned: [2] | ||
366 | 2895=Freeing RichEd20.dll failed. GetLastError() returned: [2] | ||
367 | 2896=Executing action [2] failed. | ||
368 | 2897=Failed to create any [2] font on this system. | ||
369 | 2898=For [2] text style, the system created a '[3]' font, in [4] character set. | ||
370 | 2899=Failed to create [2] text style. GetLastError() returned: [3]. | ||
371 | 2901=Invalid parameter to operation [2]: Parameter [3] | ||
372 | 2902=Operation [2] called out of sequence. | ||
373 | 2903=The file [2] is missing. | ||
374 | 2904=Could not BindImage file [2]. | ||
375 | 2905=Could not read record from script file [2]. | ||
376 | 2906=Missing header in script file [2]. | ||
377 | 2907=Could not create secure security descriptor. Error: [2] | ||
378 | 2908=Could not register component [2]. | ||
379 | 2909=Could not unregister component [2]. | ||
380 | 2910=Could not determine user's security id. | ||
381 | 2911=Could not remove the folder [2]. | ||
382 | 2912=Could not schedule file [2] for removal on reboot. | ||
383 | 2919=No cabinet specified for compressed file: [2] | ||
384 | 2920=Source directory not specified for file [2]. | ||
385 | 2924=Script [2] version unsupported. Script version: [3], minimum version: [4], maximum version: [5]. | ||
386 | 2927=ShellFolder id [2] is invalid. | ||
387 | 2928=Exceeded maximum number of sources. Skipping source '[2]'. | ||
388 | 2929=Could not determine publishing root. Error: [2] | ||
389 | 2932=Could not create file [2] from script data. Error: [3] | ||
390 | 2933=Could not initialize rollback script [2]. | ||
391 | 2934=Could not secure transform [2]. Error [3] | ||
392 | 2935=Could not un-secure transform [2]. Error [3] | ||
393 | 2936=Could not find transform [2]. | ||
394 | 2937=The Windows Installer cannot install a system file protection catalog. Catalog: [2], Error: [3] | ||
395 | 2938=The Windows Installer cannot retrieve a system file protection catalog from the cache. Catalog: [2], Error: [3] | ||
396 | 2939=The Windows Installer cannot delete a system file protection catalog from the cache. Catalog: [2], Error: [3] | ||
397 | 2940=Directory Manager not supplied for source resolution. | ||
398 | 2941=Unable to compute the CRC for file [2]. | ||
399 | 2942=BindImage action has not been executed on [2] file. | ||
400 | 2943=This version of Windows does not support deploying 64-bit packages. The script [2] is for a 64-bit package. | ||
401 | 2944=GetProductAssignmentType failed. | ||
402 | 2945=Installation of ComPlus App [2] failed with error [3]. | ||
403 | 3001=The patches in this list contain incorrect sequencing information: [2][3][4][5][6][7][8][9][10][11][12][13][14][15][16]. 3.0 | ||
404 | 3002=Patch [2] contains invalid sequencing information. | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs new file mode 100644 index 00000000..4954cda0 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs | |||
@@ -0,0 +1,573 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Globalization; | ||
10 | using System.Runtime.Serialization; | ||
11 | using System.Diagnostics.CodeAnalysis; | ||
12 | |||
13 | /// <summary> | ||
14 | /// Base class for Windows Installer exceptions. | ||
15 | /// </summary> | ||
16 | [Serializable] | ||
17 | public class InstallerException : SystemException | ||
18 | { | ||
19 | private int errorCode; | ||
20 | private object[] errorData; | ||
21 | |||
22 | /// <summary> | ||
23 | /// Creates a new InstallerException with a specified error message and a reference to the | ||
24 | /// inner exception that is the cause of this exception. | ||
25 | /// </summary> | ||
26 | /// <param name="msg">The message that describes the error.</param> | ||
27 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
28 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
29 | /// is raised in a catch block that handles the inner exception.</param> | ||
30 | public InstallerException(string msg, Exception innerException) | ||
31 | : this(0, msg, innerException) | ||
32 | { | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Creates a new InstallerException with a specified error message. | ||
37 | /// </summary> | ||
38 | /// <param name="msg">The message that describes the error.</param> | ||
39 | public InstallerException(string msg) | ||
40 | : this(0, msg) | ||
41 | { | ||
42 | } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Creates a new InstallerException. | ||
46 | /// </summary> | ||
47 | public InstallerException() | ||
48 | : this(0, null) | ||
49 | { | ||
50 | } | ||
51 | |||
52 | internal InstallerException(int errorCode, string msg, Exception innerException) | ||
53 | : base(msg, innerException) | ||
54 | { | ||
55 | this.errorCode = errorCode; | ||
56 | this.SaveErrorRecord(); | ||
57 | } | ||
58 | |||
59 | internal InstallerException(int errorCode, string msg) | ||
60 | : this(errorCode, msg, null) | ||
61 | { | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Initializes a new instance of the InstallerException class with serialized data. | ||
66 | /// </summary> | ||
67 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
68 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
69 | protected InstallerException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
70 | { | ||
71 | if (info == null) | ||
72 | { | ||
73 | throw new ArgumentNullException("info"); | ||
74 | } | ||
75 | |||
76 | this.errorCode = info.GetInt32("msiErrorCode"); | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Gets the system error code that resulted in this exception, or 0 if not applicable. | ||
81 | /// </summary> | ||
82 | public int ErrorCode | ||
83 | { | ||
84 | get | ||
85 | { | ||
86 | return this.errorCode; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | /// <summary> | ||
91 | /// Gets a message that describes the exception. This message may contain detailed | ||
92 | /// formatted error data if it was available. | ||
93 | /// </summary> | ||
94 | public override String Message | ||
95 | { | ||
96 | get | ||
97 | { | ||
98 | string msg = base.Message; | ||
99 | using (Record errorRec = this.GetErrorRecord()) | ||
100 | { | ||
101 | if (errorRec != null) | ||
102 | { | ||
103 | string errorMsg = Installer.GetErrorMessage(errorRec, CultureInfo.InvariantCulture); | ||
104 | msg = Combine(msg, errorMsg); | ||
105 | } | ||
106 | } | ||
107 | return msg; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /// <summary> | ||
112 | /// Sets the SerializationInfo with information about the exception. | ||
113 | /// </summary> | ||
114 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
115 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
116 | public override void GetObjectData(SerializationInfo info, StreamingContext context) | ||
117 | { | ||
118 | if (info == null) | ||
119 | { | ||
120 | throw new ArgumentNullException("info"); | ||
121 | } | ||
122 | |||
123 | info.AddValue("msiErrorCode", this.errorCode); | ||
124 | base.GetObjectData(info, context); | ||
125 | } | ||
126 | |||
127 | /// <summary> | ||
128 | /// Gets extended information about the error, or null if no further information | ||
129 | /// is available. | ||
130 | /// </summary> | ||
131 | /// <returns>A Record object. Field 1 of the Record contains the installer | ||
132 | /// message code. Other fields contain data specific to the particular error.</returns> | ||
133 | /// <remarks><p> | ||
134 | /// If the record is passed to <see cref="Session.Message"/>, it is formatted | ||
135 | /// by looking up the string in the current database. If there is no installation | ||
136 | /// session, the formatted error message may be obtained by a query on the Error table using | ||
137 | /// the error code, followed by a call to <see cref="Record.ToString()"/>. | ||
138 | /// Alternatively, the standard MSI message can by retrieved by calling the | ||
139 | /// <see cref="Installer.GetErrorMessage(Record,CultureInfo)"/> method. | ||
140 | /// </p><p> | ||
141 | /// The following methods and properties may report extended error data: | ||
142 | /// <list type="bullet"> | ||
143 | /// <item><see cref="Database"/> (constructor)</item> | ||
144 | /// <item><see cref="Database"/>.<see cref="Database.ApplyTransform(string,TransformErrors)"/></item> | ||
145 | /// <item><see cref="Database"/>.<see cref="Database.Commit"/></item> | ||
146 | /// <item><see cref="Database"/>.<see cref="Database.Execute(string,object[])"/></item> | ||
147 | /// <item><see cref="Database"/>.<see cref="Database.ExecuteQuery(string,object[])"/></item> | ||
148 | /// <item><see cref="Database"/>.<see cref="Database.ExecuteIntegerQuery(string,object[])"/></item> | ||
149 | /// <item><see cref="Database"/>.<see cref="Database.ExecuteStringQuery(string,object[])"/></item> | ||
150 | /// <item><see cref="Database"/>.<see cref="Database.Export"/></item> | ||
151 | /// <item><see cref="Database"/>.<see cref="Database.ExportAll"/></item> | ||
152 | /// <item><see cref="Database"/>.<see cref="Database.GenerateTransform"/></item> | ||
153 | /// <item><see cref="Database"/>.<see cref="Database.Import"/></item> | ||
154 | /// <item><see cref="Database"/>.<see cref="Database.ImportAll"/></item> | ||
155 | /// <item><see cref="Database"/>.<see cref="Database.Merge(Database,string)"/></item> | ||
156 | /// <item><see cref="Database"/>.<see cref="Database.OpenView"/></item> | ||
157 | /// <item><see cref="Database"/>.<see cref="Database.SummaryInfo"/></item> | ||
158 | /// <item><see cref="Database"/>.<see cref="Database.ViewTransform"/></item> | ||
159 | /// <item><see cref="View"/>.<see cref="View.Assign"/></item> | ||
160 | /// <item><see cref="View"/>.<see cref="View.Delete"/></item> | ||
161 | /// <item><see cref="View"/>.<see cref="View.Execute(Record)"/></item> | ||
162 | /// <item><see cref="View"/>.<see cref="View.Insert"/></item> | ||
163 | /// <item><see cref="View"/>.<see cref="View.InsertTemporary"/></item> | ||
164 | /// <item><see cref="View"/>.<see cref="View.Merge"/></item> | ||
165 | /// <item><see cref="View"/>.<see cref="View.Modify"/></item> | ||
166 | /// <item><see cref="View"/>.<see cref="View.Refresh"/></item> | ||
167 | /// <item><see cref="View"/>.<see cref="View.Replace"/></item> | ||
168 | /// <item><see cref="View"/>.<see cref="View.Seek"/></item> | ||
169 | /// <item><see cref="View"/>.<see cref="View.Update"/></item> | ||
170 | /// <item><see cref="View"/>.<see cref="View.Validate"/></item> | ||
171 | /// <item><see cref="View"/>.<see cref="View.ValidateFields"/></item> | ||
172 | /// <item><see cref="View"/>.<see cref="View.ValidateDelete"/></item> | ||
173 | /// <item><see cref="View"/>.<see cref="View.ValidateNew"/></item> | ||
174 | /// <item><see cref="SummaryInfo"/> (constructor)</item> | ||
175 | /// <item><see cref="Record"/>.<see cref="Record.SetStream(int,string)"/></item> | ||
176 | /// <item><see cref="Session"/>.<see cref="Session.SetInstallLevel"/></item> | ||
177 | /// <item><see cref="Session"/>.<see cref="Session.GetSourcePath"/></item> | ||
178 | /// <item><see cref="Session"/>.<see cref="Session.GetTargetPath"/></item> | ||
179 | /// <item><see cref="Session"/>.<see cref="Session.SetTargetPath"/></item> | ||
180 | /// <item><see cref="ComponentInfo"/>.<see cref="ComponentInfo.CurrentState"/></item> | ||
181 | /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.CurrentState"/></item> | ||
182 | /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.ValidStates"/></item> | ||
183 | /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.GetCost"/></item> | ||
184 | /// </list> | ||
185 | /// </p><p> | ||
186 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
187 | /// It is best that the handle be closed manually as soon as it is no longer | ||
188 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
189 | /// </p><p> | ||
190 | /// Win32 MSI API: | ||
191 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlasterrorrecord.asp">MsiGetLastErrorRecord</a> | ||
192 | /// </p></remarks> | ||
193 | [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] | ||
194 | public Record GetErrorRecord() | ||
195 | { | ||
196 | return this.errorData != null ? new Record(this.errorData) : null; | ||
197 | } | ||
198 | |||
199 | internal static Exception ExceptionFromReturnCode(uint errorCode) | ||
200 | { | ||
201 | return ExceptionFromReturnCode(errorCode, null); | ||
202 | } | ||
203 | |||
204 | internal static Exception ExceptionFromReturnCode(uint errorCode, string msg) | ||
205 | { | ||
206 | msg = Combine(GetSystemMessage(errorCode), msg); | ||
207 | switch (errorCode) | ||
208 | { | ||
209 | case (uint) NativeMethods.Error.FILE_NOT_FOUND: | ||
210 | case (uint) NativeMethods.Error.PATH_NOT_FOUND: return new FileNotFoundException(msg); | ||
211 | |||
212 | case (uint) NativeMethods.Error.INVALID_PARAMETER: | ||
213 | case (uint) NativeMethods.Error.DIRECTORY: | ||
214 | case (uint) NativeMethods.Error.UNKNOWN_PROPERTY: | ||
215 | case (uint) NativeMethods.Error.UNKNOWN_PRODUCT: | ||
216 | case (uint) NativeMethods.Error.UNKNOWN_FEATURE: | ||
217 | case (uint) NativeMethods.Error.UNKNOWN_COMPONENT: return new ArgumentException(msg); | ||
218 | |||
219 | case (uint) NativeMethods.Error.BAD_QUERY_SYNTAX: return new BadQuerySyntaxException(msg); | ||
220 | |||
221 | case (uint) NativeMethods.Error.INVALID_HANDLE_STATE: | ||
222 | case (uint) NativeMethods.Error.INVALID_HANDLE: | ||
223 | InvalidHandleException ihex = new InvalidHandleException(msg); | ||
224 | ihex.errorCode = (int) errorCode; | ||
225 | return ihex; | ||
226 | |||
227 | case (uint) NativeMethods.Error.INSTALL_USEREXIT: return new InstallCanceledException(msg); | ||
228 | |||
229 | case (uint) NativeMethods.Error.CALL_NOT_IMPLEMENTED: return new NotImplementedException(msg); | ||
230 | |||
231 | default: return new InstallerException((int) errorCode, msg); | ||
232 | } | ||
233 | } | ||
234 | |||
235 | internal static string GetSystemMessage(uint errorCode) | ||
236 | { | ||
237 | const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; | ||
238 | const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; | ||
239 | |||
240 | StringBuilder buf = new StringBuilder(1024); | ||
241 | uint formatCount = NativeMethods.FormatMessage( | ||
242 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
243 | IntPtr.Zero, | ||
244 | (uint) errorCode, | ||
245 | 0, | ||
246 | buf, | ||
247 | (uint) buf.Capacity, | ||
248 | IntPtr.Zero); | ||
249 | |||
250 | if (formatCount != 0) | ||
251 | { | ||
252 | return buf.ToString().Trim(); | ||
253 | } | ||
254 | else | ||
255 | { | ||
256 | return null; | ||
257 | } | ||
258 | } | ||
259 | |||
260 | internal void SaveErrorRecord() | ||
261 | { | ||
262 | // TODO: pass an affinity handle here? | ||
263 | int recordHandle = RemotableNativeMethods.MsiGetLastErrorRecord(0); | ||
264 | if (recordHandle != 0) | ||
265 | { | ||
266 | using (Record errorRec = new Record((IntPtr) recordHandle, true, null)) | ||
267 | { | ||
268 | this.errorData = new object[errorRec.FieldCount]; | ||
269 | for (int i = 0; i < this.errorData.Length; i++) | ||
270 | { | ||
271 | this.errorData[i] = errorRec[i + 1]; | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | else | ||
276 | { | ||
277 | this.errorData = null; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | private static string Combine(string msg1, string msg2) | ||
282 | { | ||
283 | if (msg1 == null) return msg2; | ||
284 | if (msg2 == null) return msg1; | ||
285 | return msg1 + " " + msg2; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | /// <summary> | ||
290 | /// User Canceled the installation. | ||
291 | /// </summary> | ||
292 | [Serializable] | ||
293 | public class InstallCanceledException : InstallerException | ||
294 | { | ||
295 | /// <summary> | ||
296 | /// Creates a new InstallCanceledException with a specified error message and a reference to the | ||
297 | /// inner exception that is the cause of this exception. | ||
298 | /// </summary> | ||
299 | /// <param name="msg">The message that describes the error.</param> | ||
300 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
301 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
302 | /// is raised in a catch block that handles the inner exception.</param> | ||
303 | public InstallCanceledException(string msg, Exception innerException) | ||
304 | : base((int) NativeMethods.Error.INSTALL_USEREXIT, msg, innerException) | ||
305 | { | ||
306 | } | ||
307 | |||
308 | /// <summary> | ||
309 | /// Creates a new InstallCanceledException with a specified error message. | ||
310 | /// </summary> | ||
311 | /// <param name="msg">The message that describes the error.</param> | ||
312 | public InstallCanceledException(string msg) | ||
313 | : this(msg, null) | ||
314 | { | ||
315 | } | ||
316 | |||
317 | /// <summary> | ||
318 | /// Creates a new InstallCanceledException. | ||
319 | /// </summary> | ||
320 | public InstallCanceledException() | ||
321 | : this(null, null) | ||
322 | { | ||
323 | } | ||
324 | |||
325 | /// <summary> | ||
326 | /// Initializes a new instance of the InstallCanceledException class with serialized data. | ||
327 | /// </summary> | ||
328 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
329 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
330 | protected InstallCanceledException(SerializationInfo info, StreamingContext context) | ||
331 | : base(info, context) | ||
332 | { | ||
333 | } | ||
334 | } | ||
335 | |||
336 | /// <summary> | ||
337 | /// A bad SQL query string was passed to <see cref="Database.OpenView"/> or <see cref="Database.Execute(string,object[])"/>. | ||
338 | /// </summary> | ||
339 | [Serializable] | ||
340 | public class BadQuerySyntaxException : InstallerException | ||
341 | { | ||
342 | /// <summary> | ||
343 | /// Creates a new BadQuerySyntaxException with a specified error message and a reference to the | ||
344 | /// inner exception that is the cause of this exception. | ||
345 | /// </summary> | ||
346 | /// <param name="msg">The message that describes the error.</param> | ||
347 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
348 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
349 | /// is raised in a catch block that handles the inner exception.</param> | ||
350 | public BadQuerySyntaxException(string msg, Exception innerException) | ||
351 | : base((int) NativeMethods.Error.BAD_QUERY_SYNTAX, msg, innerException) | ||
352 | { | ||
353 | } | ||
354 | |||
355 | /// <summary> | ||
356 | /// Creates a new BadQuerySyntaxException with a specified error message. | ||
357 | /// </summary> | ||
358 | /// <param name="msg">The message that describes the error.</param> | ||
359 | public BadQuerySyntaxException(string msg) | ||
360 | : this(msg, null) | ||
361 | { | ||
362 | } | ||
363 | |||
364 | /// <summary> | ||
365 | /// Creates a new BadQuerySyntaxException. | ||
366 | /// </summary> | ||
367 | public BadQuerySyntaxException() | ||
368 | : this(null, null) | ||
369 | { | ||
370 | } | ||
371 | |||
372 | /// <summary> | ||
373 | /// Initializes a new instance of the BadQuerySyntaxException class with serialized data. | ||
374 | /// </summary> | ||
375 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
376 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
377 | protected BadQuerySyntaxException(SerializationInfo info, StreamingContext context) | ||
378 | : base(info, context) | ||
379 | { | ||
380 | } | ||
381 | } | ||
382 | |||
383 | /// <summary> | ||
384 | /// A method was called on an invalid installer handle. The handle may have been already closed. | ||
385 | /// </summary> | ||
386 | [Serializable] | ||
387 | public class InvalidHandleException : InstallerException | ||
388 | { | ||
389 | /// <summary> | ||
390 | /// Creates a new InvalidHandleException with a specified error message and a reference to the | ||
391 | /// inner exception that is the cause of this exception. | ||
392 | /// </summary> | ||
393 | /// <param name="msg">The message that describes the error.</param> | ||
394 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
395 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
396 | /// is raised in a catch block that handles the inner exception.</param> | ||
397 | public InvalidHandleException(string msg, Exception innerException) | ||
398 | : base((int) NativeMethods.Error.INVALID_HANDLE, msg, innerException) | ||
399 | { | ||
400 | } | ||
401 | |||
402 | /// <summary> | ||
403 | /// Creates a new InvalidHandleException with a specified error message. | ||
404 | /// </summary> | ||
405 | /// <param name="msg">The message that describes the error.</param> | ||
406 | public InvalidHandleException(string msg) | ||
407 | : this(msg, null) | ||
408 | { | ||
409 | } | ||
410 | |||
411 | /// <summary> | ||
412 | /// Creates a new InvalidHandleException. | ||
413 | /// </summary> | ||
414 | public InvalidHandleException() | ||
415 | : this(null, null) | ||
416 | { | ||
417 | } | ||
418 | |||
419 | /// <summary> | ||
420 | /// Initializes a new instance of the InvalidHandleException class with serialized data. | ||
421 | /// </summary> | ||
422 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
423 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
424 | protected InvalidHandleException(SerializationInfo info, StreamingContext context) | ||
425 | : base(info, context) | ||
426 | { | ||
427 | } | ||
428 | } | ||
429 | |||
430 | /// <summary> | ||
431 | /// A failure occurred when executing <see cref="Database.Merge(Database,string)"/>. The exception may contain | ||
432 | /// details about the merge conflict. | ||
433 | /// </summary> | ||
434 | [Serializable] | ||
435 | public class MergeException : InstallerException | ||
436 | { | ||
437 | private IList<string> conflictTables; | ||
438 | private IList<int> conflictCounts; | ||
439 | |||
440 | /// <summary> | ||
441 | /// Creates a new MergeException with a specified error message and a reference to the | ||
442 | /// inner exception that is the cause of this exception. | ||
443 | /// </summary> | ||
444 | /// <param name="msg">The message that describes the error.</param> | ||
445 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
446 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
447 | /// is raised in a catch block that handles the inner exception.</param> | ||
448 | public MergeException(string msg, Exception innerException) | ||
449 | : base(msg, innerException) | ||
450 | { | ||
451 | } | ||
452 | |||
453 | /// <summary> | ||
454 | /// Creates a new MergeException with a specified error message. | ||
455 | /// </summary> | ||
456 | /// <param name="msg">The message that describes the error.</param> | ||
457 | public MergeException(string msg) | ||
458 | : base(msg) | ||
459 | { | ||
460 | } | ||
461 | |||
462 | /// <summary> | ||
463 | /// Creates a new MergeException. | ||
464 | /// </summary> | ||
465 | public MergeException() | ||
466 | : base() | ||
467 | { | ||
468 | } | ||
469 | |||
470 | internal MergeException(Database db, string conflictsTableName) | ||
471 | : base("Merge failed.") | ||
472 | { | ||
473 | if (conflictsTableName != null) | ||
474 | { | ||
475 | IList<string> conflictTableList = new List<string>(); | ||
476 | IList<int> conflictCountList = new List<int>(); | ||
477 | |||
478 | using (View view = db.OpenView("SELECT `Table`, `NumRowMergeConflicts` FROM `" + conflictsTableName + "`")) | ||
479 | { | ||
480 | view.Execute(); | ||
481 | |||
482 | foreach (Record rec in view) using (rec) | ||
483 | { | ||
484 | conflictTableList.Add(rec.GetString(1)); | ||
485 | conflictCountList.Add((int) rec.GetInteger(2)); | ||
486 | } | ||
487 | } | ||
488 | |||
489 | this.conflictTables = conflictTableList; | ||
490 | this.conflictCounts = conflictCountList; | ||
491 | } | ||
492 | } | ||
493 | |||
494 | /// <summary> | ||
495 | /// Initializes a new instance of the MergeException class with serialized data. | ||
496 | /// </summary> | ||
497 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
498 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
499 | protected MergeException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
500 | { | ||
501 | if (info == null) | ||
502 | { | ||
503 | throw new ArgumentNullException("info"); | ||
504 | } | ||
505 | |||
506 | this.conflictTables = (string[]) info.GetValue("mergeConflictTables", typeof(string[])); | ||
507 | this.conflictCounts = (int[]) info.GetValue("mergeConflictCounts", typeof(int[])); | ||
508 | } | ||
509 | |||
510 | /// <summary> | ||
511 | /// Gets the number of merge conflicts in each table, corresponding to the tables returned by | ||
512 | /// <see cref="ConflictTables"/>. | ||
513 | /// </summary> | ||
514 | public IList<int> ConflictCounts | ||
515 | { | ||
516 | get | ||
517 | { | ||
518 | return new List<int>(this.conflictCounts); | ||
519 | } | ||
520 | } | ||
521 | |||
522 | /// <summary> | ||
523 | /// Gets the list of tables containing merge conflicts. | ||
524 | /// </summary> | ||
525 | public IList<string> ConflictTables | ||
526 | { | ||
527 | get | ||
528 | { | ||
529 | return new List<string>(this.conflictTables); | ||
530 | } | ||
531 | } | ||
532 | |||
533 | /// <summary> | ||
534 | /// Gets a message that describes the merge conflits. | ||
535 | /// </summary> | ||
536 | public override String Message | ||
537 | { | ||
538 | get | ||
539 | { | ||
540 | StringBuilder msg = new StringBuilder(base.Message); | ||
541 | if (this.conflictTables != null) | ||
542 | { | ||
543 | for (int i = 0; i < this.conflictTables.Count; i++) | ||
544 | { | ||
545 | msg.Append(i == 0 ? " Conflicts: " : ", "); | ||
546 | msg.Append(this.conflictTables[i]); | ||
547 | msg.Append('('); | ||
548 | msg.Append(this.conflictCounts[i]); | ||
549 | msg.Append(')'); | ||
550 | } | ||
551 | } | ||
552 | return msg.ToString(); | ||
553 | } | ||
554 | } | ||
555 | |||
556 | /// <summary> | ||
557 | /// Sets the SerializationInfo with information about the exception. | ||
558 | /// </summary> | ||
559 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
560 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
561 | public override void GetObjectData(SerializationInfo info, StreamingContext context) | ||
562 | { | ||
563 | if (info == null) | ||
564 | { | ||
565 | throw new ArgumentNullException("info"); | ||
566 | } | ||
567 | |||
568 | info.AddValue("mergeConflictTables", this.conflictTables); | ||
569 | info.AddValue("mergeConflictCounts", this.conflictCounts); | ||
570 | base.GetObjectData(info, context); | ||
571 | } | ||
572 | } | ||
573 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs new file mode 100644 index 00000000..08f00867 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs | |||
@@ -0,0 +1,223 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Runtime.InteropServices; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Defines a callback function that the installer calls for progress notification and error messages. | ||
12 | /// </summary> | ||
13 | public delegate MessageResult ExternalUIHandler( | ||
14 | InstallMessage messageType, | ||
15 | string message, | ||
16 | MessageButtons buttons, | ||
17 | MessageIcon icon, | ||
18 | MessageDefaultButton defaultButton); | ||
19 | |||
20 | /// <summary> | ||
21 | /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages. | ||
22 | /// </summary> | ||
23 | public delegate MessageResult ExternalUIRecordHandler( | ||
24 | InstallMessage messageType, | ||
25 | Record messageRecord, | ||
26 | MessageButtons buttons, | ||
27 | MessageIcon icon, | ||
28 | MessageDefaultButton defaultButton); | ||
29 | |||
30 | internal delegate int NativeExternalUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); | ||
31 | |||
32 | internal delegate int NativeExternalUIRecordHandler(IntPtr context, int messageType, int recordHandle); | ||
33 | |||
34 | internal class ExternalUIProxy | ||
35 | { | ||
36 | private ExternalUIHandler handler; | ||
37 | |||
38 | internal ExternalUIProxy(ExternalUIHandler handler) | ||
39 | { | ||
40 | this.handler = handler; | ||
41 | } | ||
42 | |||
43 | public ExternalUIHandler Handler | ||
44 | { | ||
45 | get { return this.handler; } | ||
46 | } | ||
47 | |||
48 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
49 | public int ProxyHandler(IntPtr contextPtr, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message) | ||
50 | { | ||
51 | try | ||
52 | { | ||
53 | int msgType = messageType & 0x7F000000; | ||
54 | int buttons = messageType & 0x0000000F; | ||
55 | int icon = messageType & 0x000000F0; | ||
56 | int defButton = messageType & 0x00000F00; | ||
57 | |||
58 | return (int) this.handler( | ||
59 | (InstallMessage) msgType, | ||
60 | message, | ||
61 | (MessageButtons) buttons, | ||
62 | (MessageIcon) icon, | ||
63 | (MessageDefaultButton) defButton); | ||
64 | } | ||
65 | catch | ||
66 | { | ||
67 | return (int) MessageResult.Error; | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | internal class ExternalUIRecordProxy | ||
73 | { | ||
74 | private ExternalUIRecordHandler handler; | ||
75 | |||
76 | internal ExternalUIRecordProxy(ExternalUIRecordHandler handler) | ||
77 | { | ||
78 | this.handler = handler; | ||
79 | } | ||
80 | |||
81 | public ExternalUIRecordHandler Handler | ||
82 | { | ||
83 | get { return this.handler; } | ||
84 | } | ||
85 | |||
86 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | ||
87 | public int ProxyHandler(IntPtr contextPtr, int messageType, int recordHandle) | ||
88 | { | ||
89 | try | ||
90 | { | ||
91 | int msgType = messageType & 0x7F000000; | ||
92 | int buttons = messageType & 0x0000000F; | ||
93 | int icon = messageType & 0x000000F0; | ||
94 | int defButton = messageType & 0x00000F00; | ||
95 | |||
96 | Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null); | ||
97 | using (msgRec) | ||
98 | { | ||
99 | return (int) this.handler( | ||
100 | (InstallMessage) msgType, | ||
101 | msgRec, | ||
102 | (MessageButtons) buttons, | ||
103 | (MessageIcon) icon, | ||
104 | (MessageDefaultButton) defButton); | ||
105 | } | ||
106 | } | ||
107 | catch | ||
108 | { | ||
109 | return (int) MessageResult.Error; | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | public static partial class Installer | ||
115 | { | ||
116 | private static IList externalUIHandlers = ArrayList.Synchronized(new ArrayList()); | ||
117 | |||
118 | /// <summary> | ||
119 | /// Enables an external user-interface handler. This external UI handler is called before the | ||
120 | /// normal internal user-interface handler. The external UI handler has the option to suppress | ||
121 | /// the internal UI by returning a non-zero value to indicate that it has handled the messages. | ||
122 | /// </summary> | ||
123 | /// <param name="uiHandler">A callback delegate that handles the UI messages</param> | ||
124 | /// <param name="messageFilter">Specifies which messages to handle using the external message handler. | ||
125 | /// If the external handler returns a non-zero result, then that message will not be sent to the UI, | ||
126 | /// instead the message will be logged if logging has been enabled.</param> | ||
127 | /// <returns>The previously set external handler, or null if there was no previously set handler</returns> | ||
128 | /// <remarks><p> | ||
129 | /// To restore the previous UI handler, a second call is made to SetExternalUI using the | ||
130 | /// ExternalUIHandler returned by the first call to SetExternalUI and specifying | ||
131 | /// <see cref="InstallLogModes.None"/> as the message filter. | ||
132 | /// </p><p> | ||
133 | /// The external user interface handler does not have full control over the external user | ||
134 | /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to | ||
135 | /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user | ||
136 | /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not | ||
137 | /// handled by the external user interface handler is handled by Windows Installer. The initial | ||
138 | /// "Preparing to install..." dialog always appears even if the external user interface | ||
139 | /// handler handles all messages. | ||
140 | /// </p><p> | ||
141 | /// SetExternalUI should only be called from a bootstrapping application. You cannot call | ||
142 | /// it from a custom action | ||
143 | /// </p><p> | ||
144 | /// Win32 MSI API: | ||
145 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternalui.asp">MsiSetExternalUI</a> | ||
146 | /// </p></remarks> | ||
147 | public static ExternalUIHandler SetExternalUI(ExternalUIHandler uiHandler, InstallLogModes messageFilter) | ||
148 | { | ||
149 | NativeExternalUIHandler nativeHandler = null; | ||
150 | if (uiHandler != null) | ||
151 | { | ||
152 | nativeHandler = new ExternalUIProxy(uiHandler).ProxyHandler; | ||
153 | Installer.externalUIHandlers.Add(nativeHandler); | ||
154 | } | ||
155 | NativeExternalUIHandler oldNativeHandler = NativeMethods.MsiSetExternalUI(nativeHandler, (uint) messageFilter, IntPtr.Zero); | ||
156 | if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIProxy) | ||
157 | { | ||
158 | Installer.externalUIHandlers.Remove(oldNativeHandler); | ||
159 | return ((ExternalUIProxy) oldNativeHandler.Target).Handler; | ||
160 | } | ||
161 | else | ||
162 | { | ||
163 | return null; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called | ||
169 | /// before the normal internal user-interface handler. The external UI handler has the option to suppress | ||
170 | /// the internal UI by returning a non-zero value to indicate that it has handled the messages. | ||
171 | /// </summary> | ||
172 | /// <param name="uiHandler">A callback delegate that handles the UI messages</param> | ||
173 | /// <param name="messageFilter">Specifies which messages to handle using the external message handler. | ||
174 | /// If the external handler returns a non-zero result, then that message will not be sent to the UI, | ||
175 | /// instead the message will be logged if logging has been enabled.</param> | ||
176 | /// <returns>The previously set external handler, or null if there was no previously set handler</returns> | ||
177 | /// <remarks><p> | ||
178 | /// To restore the previous UI handler, a second call is made to SetExternalUI using the | ||
179 | /// ExternalUIHandler returned by the first call to SetExternalUI and specifying | ||
180 | /// <see cref="InstallLogModes.None"/> as the message filter. | ||
181 | /// </p><p> | ||
182 | /// The external user interface handler does not have full control over the external user | ||
183 | /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to | ||
184 | /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user | ||
185 | /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not | ||
186 | /// handled by the external user interface handler is handled by Windows Installer. The initial | ||
187 | /// "Preparing to install..." dialog always appears even if the external user interface | ||
188 | /// handler handles all messages. | ||
189 | /// </p><p> | ||
190 | /// SetExternalUI should only be called from a bootstrapping application. You cannot call | ||
191 | /// it from a custom action | ||
192 | /// </p><p> | ||
193 | /// Win32 MSI API: | ||
194 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternaluirecord.asp">MsiSetExternalUIRecord</a> | ||
195 | /// </p></remarks> | ||
196 | public static ExternalUIRecordHandler SetExternalUI(ExternalUIRecordHandler uiHandler, InstallLogModes messageFilter) | ||
197 | { | ||
198 | NativeExternalUIRecordHandler nativeHandler = null; | ||
199 | if (uiHandler != null) | ||
200 | { | ||
201 | nativeHandler = new ExternalUIRecordProxy(uiHandler).ProxyHandler; | ||
202 | Installer.externalUIHandlers.Add(nativeHandler); | ||
203 | } | ||
204 | NativeExternalUIRecordHandler oldNativeHandler; | ||
205 | uint ret = NativeMethods.MsiSetExternalUIRecord(nativeHandler, (uint) messageFilter, IntPtr.Zero, out oldNativeHandler); | ||
206 | if (ret != 0) | ||
207 | { | ||
208 | Installer.externalUIHandlers.Remove(nativeHandler); | ||
209 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
210 | } | ||
211 | |||
212 | if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIRecordProxy) | ||
213 | { | ||
214 | Installer.externalUIHandlers.Remove(oldNativeHandler); | ||
215 | return ((ExternalUIRecordProxy) oldNativeHandler.Target).Handler; | ||
216 | } | ||
217 | else | ||
218 | { | ||
219 | return null; | ||
220 | } | ||
221 | } | ||
222 | } | ||
223 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs new file mode 100644 index 00000000..9a1a859a --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs | |||
@@ -0,0 +1,497 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections; | ||
8 | using System.Collections.Generic; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Accessor for information about features within the context of an installation session. | ||
12 | /// </summary> | ||
13 | public sealed class FeatureInfoCollection : ICollection<FeatureInfo> | ||
14 | { | ||
15 | private Session session; | ||
16 | |||
17 | internal FeatureInfoCollection(Session session) | ||
18 | { | ||
19 | this.session = session; | ||
20 | } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Gets information about a feature within the context of an installation session. | ||
24 | /// </summary> | ||
25 | /// <param name="feature">name of the feature</param> | ||
26 | /// <returns>feature object</returns> | ||
27 | public FeatureInfo this[string feature] | ||
28 | { | ||
29 | get | ||
30 | { | ||
31 | return new FeatureInfo(this.session, feature); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | void ICollection<FeatureInfo>.Add(FeatureInfo item) | ||
36 | { | ||
37 | throw new InvalidOperationException(); | ||
38 | } | ||
39 | |||
40 | void ICollection<FeatureInfo>.Clear() | ||
41 | { | ||
42 | throw new InvalidOperationException(); | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Checks if the collection contains a feature. | ||
47 | /// </summary> | ||
48 | /// <param name="feature">name of the feature</param> | ||
49 | /// <returns>true if the feature is in the collection, else false</returns> | ||
50 | public bool Contains(string feature) | ||
51 | { | ||
52 | return this.session.Database.CountRows( | ||
53 | "Feature", "`Feature` = '" + feature + "'") == 1; | ||
54 | } | ||
55 | |||
56 | bool ICollection<FeatureInfo>.Contains(FeatureInfo item) | ||
57 | { | ||
58 | return item != null && this.Contains(item.Name); | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Copies the features into an array. | ||
63 | /// </summary> | ||
64 | /// <param name="array">array that receives the features</param> | ||
65 | /// <param name="arrayIndex">offset into the array</param> | ||
66 | public void CopyTo(FeatureInfo[] array, int arrayIndex) | ||
67 | { | ||
68 | foreach (FeatureInfo feature in this) | ||
69 | { | ||
70 | array[arrayIndex++] = feature; | ||
71 | } | ||
72 | } | ||
73 | |||
74 | /// <summary> | ||
75 | /// Gets the number of features defined for the product. | ||
76 | /// </summary> | ||
77 | public int Count | ||
78 | { | ||
79 | get | ||
80 | { | ||
81 | return this.session.Database.CountRows("Feature"); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | bool ICollection<FeatureInfo>.IsReadOnly | ||
86 | { | ||
87 | get | ||
88 | { | ||
89 | return true; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | bool ICollection<FeatureInfo>.Remove(FeatureInfo item) | ||
94 | { | ||
95 | throw new InvalidOperationException(); | ||
96 | } | ||
97 | |||
98 | /// <summary> | ||
99 | /// Enumerates the features in the collection. | ||
100 | /// </summary> | ||
101 | /// <returns>an enumerator over all features in the collection</returns> | ||
102 | public IEnumerator<FeatureInfo> GetEnumerator() | ||
103 | { | ||
104 | using (View featureView = this.session.Database.OpenView( | ||
105 | "SELECT `Feature` FROM `Feature`")) | ||
106 | { | ||
107 | featureView.Execute(); | ||
108 | |||
109 | foreach (Record featureRec in featureView) using (featureRec) | ||
110 | { | ||
111 | string feature = featureRec.GetString(1); | ||
112 | yield return new FeatureInfo(this.session, feature); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | IEnumerator IEnumerable.GetEnumerator() | ||
118 | { | ||
119 | return this.GetEnumerator(); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Provides access to information about a feature within the context of an installation session. | ||
125 | /// </summary> | ||
126 | public class FeatureInfo | ||
127 | { | ||
128 | private Session session; | ||
129 | private string name; | ||
130 | |||
131 | internal FeatureInfo(Session session, string name) | ||
132 | { | ||
133 | this.session = session; | ||
134 | this.name = name; | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Gets the name of the feature (primary key in the Feature table). | ||
139 | /// </summary> | ||
140 | public string Name | ||
141 | { | ||
142 | get | ||
143 | { | ||
144 | return this.name; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Gets the current install state of the feature. | ||
150 | /// </summary> | ||
151 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
152 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
153 | /// <remarks><p> | ||
154 | /// Win32 MSI API: | ||
155 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a> | ||
156 | /// </p></remarks> | ||
157 | public InstallState CurrentState | ||
158 | { | ||
159 | get | ||
160 | { | ||
161 | int installState, actionState; | ||
162 | uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState); | ||
163 | if (ret != 0) | ||
164 | { | ||
165 | if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE) | ||
166 | { | ||
167 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
168 | } | ||
169 | else | ||
170 | { | ||
171 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | if (installState == (int) InstallState.Advertised) | ||
176 | { | ||
177 | return InstallState.Advertised; | ||
178 | } | ||
179 | return (InstallState) installState; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | /// <summary> | ||
184 | /// Gets or sets the action state of the feature. | ||
185 | /// </summary> | ||
186 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
187 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
188 | /// <remarks><p> | ||
189 | /// When changing the feature action, the action state of all the Components linked to the changed | ||
190 | /// Feature records are also updated appropriately, based on the new feature Select state. | ||
191 | /// All Features can be configured at once by specifying the keyword ALL instead of a specific feature name. | ||
192 | /// </p><p> | ||
193 | /// Win32 MSI APIs: | ||
194 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a>, | ||
195 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeaturestate.asp">MsiSetFeatureState</a> | ||
196 | /// </p></remarks> | ||
197 | public InstallState RequestState | ||
198 | { | ||
199 | get | ||
200 | { | ||
201 | int installState, actionState; | ||
202 | uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState); | ||
203 | if (ret != 0) | ||
204 | { | ||
205 | if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE) | ||
206 | { | ||
207 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
208 | } | ||
209 | else | ||
210 | { | ||
211 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
212 | } | ||
213 | } | ||
214 | return (InstallState) actionState; | ||
215 | } | ||
216 | |||
217 | set | ||
218 | { | ||
219 | uint ret = RemotableNativeMethods.MsiSetFeatureState((int) this.session.Handle, this.name, (int) value); | ||
220 | if (ret != 0) | ||
221 | { | ||
222 | if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE) | ||
223 | { | ||
224 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
225 | } | ||
226 | else | ||
227 | { | ||
228 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
229 | } | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | |||
234 | /// <summary> | ||
235 | /// Gets a list of valid installation states for the feature. | ||
236 | /// </summary> | ||
237 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
238 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
239 | /// <remarks><p> | ||
240 | /// Win32 MSI API: | ||
241 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturevalidstates.asp">MsiGetFeatureValidStates</a> | ||
242 | /// </p></remarks> | ||
243 | public ICollection<InstallState> ValidStates | ||
244 | { | ||
245 | get | ||
246 | { | ||
247 | List<InstallState> states = new List<InstallState>(); | ||
248 | uint installState; | ||
249 | uint ret = RemotableNativeMethods.MsiGetFeatureValidStates((int) this.session.Handle, this.name, out installState); | ||
250 | if (ret != 0) | ||
251 | { | ||
252 | if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE) | ||
253 | { | ||
254 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
255 | } | ||
256 | else | ||
257 | { | ||
258 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
259 | } | ||
260 | } | ||
261 | |||
262 | for (int i = 1; i <= (int) InstallState.Default; i++) | ||
263 | { | ||
264 | if (((int) installState & (1 << i)) != 0) | ||
265 | { | ||
266 | states.Add((InstallState) i); | ||
267 | } | ||
268 | } | ||
269 | return states.AsReadOnly(); | ||
270 | } | ||
271 | } | ||
272 | |||
273 | /// <summary> | ||
274 | /// Gets or sets the attributes of the feature. | ||
275 | /// </summary> | ||
276 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
277 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
278 | /// <remarks><p> | ||
279 | /// Win32 MSI APIs: | ||
280 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>, | ||
281 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a> | ||
282 | /// </p><p> | ||
283 | /// Since the lpAttributes paramter of | ||
284 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a> | ||
285 | /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, this flag will | ||
286 | /// not be retrieved. | ||
287 | /// </p><p> | ||
288 | /// Since the dwAttributes parameter of | ||
289 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a> | ||
290 | /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, the presence | ||
291 | /// of this flag will be ignored. | ||
292 | /// </p></remarks> | ||
293 | public FeatureAttributes Attributes | ||
294 | { | ||
295 | get | ||
296 | { | ||
297 | FeatureAttributes attributes; | ||
298 | uint titleBufSize = 0; | ||
299 | uint descBufSize = 0; | ||
300 | uint attr; | ||
301 | uint ret = NativeMethods.MsiGetFeatureInfo( | ||
302 | (int) this.session.Handle, | ||
303 | this.name, | ||
304 | out attr, | ||
305 | null, | ||
306 | ref titleBufSize, | ||
307 | null, | ||
308 | ref descBufSize); | ||
309 | |||
310 | if (ret != 0) | ||
311 | { | ||
312 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
313 | } | ||
314 | |||
315 | // Values for attributes that MsiGetFeatureInfo returns are | ||
316 | // double the values in the Attributes column of the Feature Table. | ||
317 | attributes = (FeatureAttributes) (attr >> 1); | ||
318 | |||
319 | // MsiGetFeatureInfo MSDN documentation indicates | ||
320 | // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 16 | ||
321 | // which is UIDisallowAbsent. MsiGetFeatureInfo isn't documented to | ||
322 | // return an attribute for 'UIDisallowAbsent', so if UIDisallowAbsent | ||
323 | // is set, change it to NoUnsupportedAdvertise which then maps correctly | ||
324 | // to NOUNSUPPORTEDADVERTISE. | ||
325 | if ((attributes & FeatureAttributes.UIDisallowAbsent) == FeatureAttributes.UIDisallowAbsent) | ||
326 | { | ||
327 | attributes &= ~FeatureAttributes.UIDisallowAbsent; | ||
328 | attributes |= FeatureAttributes.NoUnsupportedAdvertise; | ||
329 | } | ||
330 | |||
331 | return attributes; | ||
332 | } | ||
333 | |||
334 | set | ||
335 | { | ||
336 | // MsiSetFeatureAttributes doesn't indicate UIDisallowAbsent is valid | ||
337 | // so remove it. | ||
338 | FeatureAttributes attributes = value; | ||
339 | attributes &= ~FeatureAttributes.UIDisallowAbsent; | ||
340 | |||
341 | // Values for attributes that MsiSetFeatureAttributes uses are | ||
342 | // double the values in the Attributes column of the Feature Table. | ||
343 | uint attr = ((uint) attributes) << 1; | ||
344 | |||
345 | // MsiSetFeatureAttributes MSDN documentation indicates | ||
346 | // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 64 | ||
347 | // which is undefined. Change this back to 32. | ||
348 | uint noUnsupportedAdvertiseDbl = ((uint)FeatureAttributes.NoUnsupportedAdvertise) << 1; | ||
349 | if ((attr & noUnsupportedAdvertiseDbl) == noUnsupportedAdvertiseDbl) | ||
350 | { | ||
351 | attr &= ~noUnsupportedAdvertiseDbl; | ||
352 | attr |= (uint) FeatureAttributes.NoUnsupportedAdvertise; | ||
353 | } | ||
354 | |||
355 | uint ret = RemotableNativeMethods.MsiSetFeatureAttributes((int) this.session.Handle, this.name, attr); | ||
356 | |||
357 | if (ret != (uint)NativeMethods.Error.SUCCESS) | ||
358 | { | ||
359 | if (ret == (uint)NativeMethods.Error.UNKNOWN_FEATURE) | ||
360 | { | ||
361 | throw InstallerException.ExceptionFromReturnCode(ret, this.name); | ||
362 | } | ||
363 | else | ||
364 | { | ||
365 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
366 | } | ||
367 | } | ||
368 | } | ||
369 | } | ||
370 | |||
371 | /// <summary> | ||
372 | /// Gets the title of the feature. | ||
373 | /// </summary> | ||
374 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
375 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
376 | /// <remarks><p> | ||
377 | /// Win32 MSI API: | ||
378 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a> | ||
379 | /// </p></remarks> | ||
380 | public string Title | ||
381 | { | ||
382 | get | ||
383 | { | ||
384 | StringBuilder titleBuf = new StringBuilder(80); | ||
385 | uint titleBufSize = (uint) titleBuf.Capacity; | ||
386 | uint descBufSize = 0; | ||
387 | uint attr; | ||
388 | uint ret = NativeMethods.MsiGetFeatureInfo( | ||
389 | (int) this.session.Handle, | ||
390 | this.name, | ||
391 | out attr, | ||
392 | titleBuf, | ||
393 | ref titleBufSize, | ||
394 | null, | ||
395 | ref descBufSize); | ||
396 | |||
397 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
398 | { | ||
399 | titleBuf.Capacity = (int) ++titleBufSize; | ||
400 | ret = NativeMethods.MsiGetFeatureInfo( | ||
401 | (int) this.session.Handle, | ||
402 | this.name, | ||
403 | out attr, | ||
404 | titleBuf, | ||
405 | ref titleBufSize, | ||
406 | null, | ||
407 | ref descBufSize); | ||
408 | } | ||
409 | |||
410 | if (ret != 0) | ||
411 | { | ||
412 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
413 | } | ||
414 | |||
415 | return titleBuf.ToString(); | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /// <summary> | ||
420 | /// Gets the description of the feature. | ||
421 | /// </summary> | ||
422 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
423 | /// <exception cref="ArgumentException">an unknown feature was requested</exception> | ||
424 | /// <remarks><p> | ||
425 | /// Win32 MSI API: | ||
426 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a> | ||
427 | /// </p></remarks> | ||
428 | public string Description | ||
429 | { | ||
430 | get | ||
431 | { | ||
432 | StringBuilder descBuf = new StringBuilder(256); | ||
433 | uint titleBufSize = 0; | ||
434 | uint descBufSize = (uint) descBuf.Capacity; | ||
435 | uint attr; | ||
436 | uint ret = NativeMethods.MsiGetFeatureInfo( | ||
437 | (int) this.session.Handle, | ||
438 | this.name, | ||
439 | out attr, | ||
440 | null, | ||
441 | ref titleBufSize, | ||
442 | descBuf, | ||
443 | ref descBufSize); | ||
444 | |||
445 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
446 | { | ||
447 | descBuf.Capacity = (int) ++descBufSize; | ||
448 | ret = NativeMethods.MsiGetFeatureInfo( | ||
449 | (int) this.session.Handle, | ||
450 | this.name, | ||
451 | out attr, | ||
452 | null, | ||
453 | ref titleBufSize, | ||
454 | descBuf, | ||
455 | ref descBufSize); | ||
456 | } | ||
457 | |||
458 | if (ret != 0) | ||
459 | { | ||
460 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
461 | } | ||
462 | |||
463 | return descBuf.ToString(); | ||
464 | } | ||
465 | } | ||
466 | |||
467 | /// <summary> | ||
468 | /// Calculates the disk space required by the feature and its selected children and parent features. | ||
469 | /// </summary> | ||
470 | /// <param name="includeParents">If true, the parent features are included in the cost.</param> | ||
471 | /// <param name="includeChildren">If true, the child features are included in the cost.</param> | ||
472 | /// <param name="installState">Specifies the installation state.</param> | ||
473 | /// <returns>The disk space requirement in bytes.</returns> | ||
474 | /// <remarks><p> | ||
475 | /// Win32 MSI API: | ||
476 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturecost.asp">MsiGetFeatureCost</a> | ||
477 | /// </p></remarks> | ||
478 | public long GetCost(bool includeParents, bool includeChildren, InstallState installState) | ||
479 | { | ||
480 | const int MSICOSTTREE_CHILDREN = 1; | ||
481 | const int MSICOSTTREE_PARENTS = 2; | ||
482 | |||
483 | int cost; | ||
484 | uint ret = RemotableNativeMethods.MsiGetFeatureCost( | ||
485 | (int) this.session.Handle, | ||
486 | this.name, | ||
487 | (includeParents ? MSICOSTTREE_PARENTS : 0) | (includeChildren ? MSICOSTTREE_CHILDREN : 0), | ||
488 | (int) installState, | ||
489 | out cost); | ||
490 | if (ret != 0) | ||
491 | { | ||
492 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
493 | } | ||
494 | return cost * 512L; | ||
495 | } | ||
496 | } | ||
497 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs new file mode 100644 index 00000000..aa8ffe34 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Represents an instance of a feature of an installed product. | ||
12 | /// </summary> | ||
13 | public class FeatureInstallation : InstallationPart | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Creates a new FeatureInstallation instance for a feature of a product. | ||
17 | /// </summary> | ||
18 | /// <param name="featureName">feature name</param> | ||
19 | /// <param name="productCode">ProductCode GUID</param> | ||
20 | public FeatureInstallation(string featureName, string productCode) | ||
21 | : base(featureName, productCode) | ||
22 | { | ||
23 | if (String.IsNullOrEmpty(featureName)) | ||
24 | { | ||
25 | throw new ArgumentNullException("featureName"); | ||
26 | } | ||
27 | } | ||
28 | |||
29 | /// <summary> | ||
30 | /// Gets the name of the feature. | ||
31 | /// </summary> | ||
32 | public string FeatureName | ||
33 | { | ||
34 | get | ||
35 | { | ||
36 | return this.Id; | ||
37 | } | ||
38 | } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Gets the installed state of the feature. | ||
42 | /// </summary> | ||
43 | /// <remarks><p> | ||
44 | /// Win32 MSI API: | ||
45 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a> | ||
46 | /// </p></remarks> | ||
47 | public override InstallState State | ||
48 | { | ||
49 | get | ||
50 | { | ||
51 | int installState = NativeMethods.MsiQueryFeatureState( | ||
52 | this.ProductCode, this.FeatureName); | ||
53 | return (InstallState) installState; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Gets the parent of the feature, or null if the feature has no parent (it is a root feature). | ||
59 | /// </summary> | ||
60 | /// <remarks> | ||
61 | /// Invocation of this property may be slightly costly for products with many features, | ||
62 | /// because it involves an enumeration of all the features in the product. | ||
63 | /// </remarks> | ||
64 | public FeatureInstallation Parent | ||
65 | { | ||
66 | get | ||
67 | { | ||
68 | StringBuilder featureBuf = new StringBuilder(256); | ||
69 | StringBuilder parentBuf = new StringBuilder(256); | ||
70 | for (uint i = 0; ; i++) | ||
71 | { | ||
72 | uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, featureBuf, parentBuf); | ||
73 | |||
74 | if (ret != 0) | ||
75 | { | ||
76 | break; | ||
77 | } | ||
78 | |||
79 | if (featureBuf.ToString() == this.FeatureName) | ||
80 | { | ||
81 | if (parentBuf.Length > 0) | ||
82 | { | ||
83 | return new FeatureInstallation(parentBuf.ToString(), this.ProductCode); | ||
84 | } | ||
85 | else | ||
86 | { | ||
87 | return null; | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
92 | return null; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Gets the usage metrics for the feature. | ||
98 | /// </summary> | ||
99 | /// <remarks><p> | ||
100 | /// If no usage metrics are recorded, the <see cref="UsageData.UseCount" /> value is 0. | ||
101 | /// </p><p> | ||
102 | /// Win32 MSI API: | ||
103 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureusage.asp">MsiGetFeatureUsage</a> | ||
104 | /// </p></remarks> | ||
105 | public FeatureInstallation.UsageData Usage | ||
106 | { | ||
107 | get | ||
108 | { | ||
109 | uint useCount; | ||
110 | ushort useDate; | ||
111 | uint ret = NativeMethods.MsiGetFeatureUsage( | ||
112 | this.ProductCode, this.FeatureName, out useCount, out useDate); | ||
113 | if (ret != 0) | ||
114 | { | ||
115 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
116 | } | ||
117 | |||
118 | DateTime lastUsedDate; | ||
119 | if (useCount == 0) | ||
120 | { | ||
121 | lastUsedDate = DateTime.MinValue; | ||
122 | } | ||
123 | else | ||
124 | { | ||
125 | lastUsedDate = new DateTime( | ||
126 | 1980 + (useDate >> 9), | ||
127 | (useDate & 0x01FF) >> 5, | ||
128 | (useDate & 0x001F)); | ||
129 | } | ||
130 | |||
131 | return new UsageData((int) useCount, lastUsedDate); | ||
132 | } | ||
133 | } | ||
134 | |||
135 | /// <summary> | ||
136 | /// Holds data about the usage of a feature. | ||
137 | /// </summary> | ||
138 | [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] | ||
139 | [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] | ||
140 | public struct UsageData | ||
141 | { | ||
142 | private int useCount; | ||
143 | private DateTime lastUsedDate; | ||
144 | |||
145 | internal UsageData(int useCount, DateTime lastUsedDate) | ||
146 | { | ||
147 | this.useCount = useCount; | ||
148 | this.lastUsedDate = lastUsedDate; | ||
149 | } | ||
150 | |||
151 | /// <summary> | ||
152 | /// Gets count of the number of times the feature has been used. | ||
153 | /// </summary> | ||
154 | public int UseCount | ||
155 | { | ||
156 | get | ||
157 | { | ||
158 | return this.useCount; | ||
159 | } | ||
160 | } | ||
161 | |||
162 | /// <summary> | ||
163 | /// Gets the date the feature was last used. | ||
164 | /// </summary> | ||
165 | public DateTime LastUsedDate | ||
166 | { | ||
167 | get | ||
168 | { | ||
169 | return this.lastUsedDate; | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs new file mode 100644 index 00000000..c3d3625f --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Base class for Windows Installer handle types (Database, View, Record, SummaryInfo). | ||
11 | /// </summary> | ||
12 | /// <remarks><p> | ||
13 | /// These classes implement the <see cref="IDisposable"/> interface, because they | ||
14 | /// hold unmanaged resources (MSI handles) that should be properly disposed | ||
15 | /// when no longer needed. | ||
16 | /// </p></remarks> | ||
17 | public abstract class InstallerHandle : MarshalByRefObject, IDisposable | ||
18 | { | ||
19 | private NativeMethods.MsiHandle handle; | ||
20 | |||
21 | /// <summary> | ||
22 | /// Constructs a handle object from a native integer handle. | ||
23 | /// </summary> | ||
24 | /// <param name="handle">Native integer handle.</param> | ||
25 | /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param> | ||
26 | protected InstallerHandle(IntPtr handle, bool ownsHandle) | ||
27 | { | ||
28 | if (handle == IntPtr.Zero) | ||
29 | { | ||
30 | throw new InvalidHandleException(); | ||
31 | } | ||
32 | |||
33 | this.handle = new NativeMethods.MsiHandle(handle, ownsHandle); | ||
34 | } | ||
35 | |||
36 | /// <summary> | ||
37 | /// Gets the native integer handle. | ||
38 | /// </summary> | ||
39 | [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] | ||
40 | public IntPtr Handle | ||
41 | { | ||
42 | get | ||
43 | { | ||
44 | if (this.IsClosed) | ||
45 | { | ||
46 | throw new InvalidHandleException(); | ||
47 | } | ||
48 | return this.handle; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | /// <summary> | ||
53 | /// Checks if the handle is closed. When closed, method calls on the handle object may throw an <see cref="InvalidHandleException"/>. | ||
54 | /// </summary> | ||
55 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
56 | public bool IsClosed | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | return this.handle.IsClosed; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>. | ||
66 | /// </summary> | ||
67 | /// <remarks><p> | ||
68 | /// The finalizer of this class will NOT close the handle if it is still open, | ||
69 | /// because finalization can run on a separate thread from the application, | ||
70 | /// resulting in potential problems if handles are closed from that thread. | ||
71 | /// It is best that the handle be closed manually as soon as it is no longer needed, | ||
72 | /// as leaving lots of unused handles open can degrade performance. | ||
73 | /// </p><p> | ||
74 | /// Win32 MSI API: | ||
75 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a> | ||
76 | /// </p></remarks> | ||
77 | /// <seealso cref="Close"/> | ||
78 | public void Dispose() | ||
79 | { | ||
80 | this.Dispose(true); | ||
81 | GC.SuppressFinalize(this); | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>. | ||
86 | /// </summary> | ||
87 | /// <remarks><p> | ||
88 | /// The finalizer of this class will NOT close the handle if it is still open, | ||
89 | /// because finalization can run on a separate thread from the application, | ||
90 | /// resulting in potential problems if handles are closed from that thread. | ||
91 | /// It is best that the handle be closed manually as soon as it is no longer needed, | ||
92 | /// as leaving lots of unused handles open can degrade performance. | ||
93 | /// </p><p> | ||
94 | /// This method is merely an alias for the <see cref="Dispose()"/> method. | ||
95 | /// </p><p> | ||
96 | /// Win32 MSI API: | ||
97 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a> | ||
98 | /// </p></remarks> | ||
99 | public void Close() | ||
100 | { | ||
101 | this.Dispose(); | ||
102 | } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Tests whether this handle object is equal to another handle object. Two handle objects are equal | ||
106 | /// if their types are the same and their native integer handles are the same. | ||
107 | /// </summary> | ||
108 | /// <param name="obj">The handle object to compare with the current handle object.</param> | ||
109 | /// <returns>true if the specified handle object is equal to the current handle object; otherwise false</returns> | ||
110 | public override bool Equals(object obj) | ||
111 | { | ||
112 | return (obj != null && this.GetType() == obj.GetType() && | ||
113 | this.Handle == ((InstallerHandle) obj).Handle); | ||
114 | } | ||
115 | |||
116 | /// <summary> | ||
117 | /// Gets a hash value for the handle object. | ||
118 | /// </summary> | ||
119 | /// <returns>A hash code for the handle object.</returns> | ||
120 | /// <remarks><p> | ||
121 | /// The hash code is derived from the native integer handle. | ||
122 | /// </p></remarks> | ||
123 | public override int GetHashCode() | ||
124 | { | ||
125 | return this.Handle.GetHashCode(); | ||
126 | } | ||
127 | |||
128 | /// <summary> | ||
129 | /// Gets an object that can be used internally for safe syncronization. | ||
130 | /// </summary> | ||
131 | internal object Sync | ||
132 | { | ||
133 | get | ||
134 | { | ||
135 | return this.handle; | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>. | ||
141 | /// </summary> | ||
142 | /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code, | ||
143 | /// so managed and unmanaged resources will be disposed. If false, the method has been called by the | ||
144 | /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param> | ||
145 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
146 | protected virtual void Dispose(bool disposing) | ||
147 | { | ||
148 | if (disposing) | ||
149 | { | ||
150 | this.handle.Dispose(); | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs new file mode 100644 index 00000000..d77c82a9 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs | |||
@@ -0,0 +1,67 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System.Diagnostics.CodeAnalysis; | ||
6 | |||
7 | /// <summary> | ||
8 | /// [MSI 4.5] Interface for an embedded external user interface for an installation. | ||
9 | /// </summary> | ||
10 | /// <remarks> | ||
11 | /// Classes which implement this interface must have a public constructor that takes no parameters. | ||
12 | /// </remarks> | ||
13 | public interface IEmbeddedUI | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Initializes the embedded UI. | ||
17 | /// </summary> | ||
18 | /// <param name="session">Handle to the installer which can be used to get and set properties. | ||
19 | /// The handle is only valid for the duration of this method call.</param> | ||
20 | /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param> | ||
21 | /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this | ||
22 | /// method returns, the installer resets the UI level to the returned value of this parameter.</param> | ||
23 | /// <returns>True if the embedded UI was successfully initialized; false if the installation | ||
24 | /// should continue without the embedded UI.</returns> | ||
25 | /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception> | ||
26 | /// <exception cref="InstallerException">The embedded UI failed to initialize and | ||
27 | /// causes the installation to fail.</exception> | ||
28 | /// <remarks><p> | ||
29 | /// Win32 MSI API: | ||
30 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/initializeembeddedui.asp">InitializeEmbeddedUI</a> | ||
31 | /// </p></remarks> | ||
32 | [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] | ||
33 | bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel); | ||
34 | |||
35 | /// <summary> | ||
36 | /// Processes information and progress messages sent to the user interface. | ||
37 | /// </summary> | ||
38 | /// <param name="messageType">Message type.</param> | ||
39 | /// <param name="messageRecord">Record that contains message data.</param> | ||
40 | /// <param name="buttons">Message buttons.</param> | ||
41 | /// <param name="icon">Message box icon.</param> | ||
42 | /// <param name="defaultButton">Message box default button.</param> | ||
43 | /// <returns>Result of processing the message.</returns> | ||
44 | /// <remarks><p> | ||
45 | /// Win32 MSI API: | ||
46 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/embeddeduihandler.asp">EmbeddedUIHandler</a> | ||
47 | /// </p></remarks> | ||
48 | MessageResult ProcessMessage( | ||
49 | InstallMessage messageType, | ||
50 | Record messageRecord, | ||
51 | MessageButtons buttons, | ||
52 | MessageIcon icon, | ||
53 | MessageDefaultButton defaultButton); | ||
54 | |||
55 | /// <summary> | ||
56 | /// Shuts down the embedded UI at the end of the installation. | ||
57 | /// </summary> | ||
58 | /// <remarks> | ||
59 | /// If the installation was canceled during initialization, this method will not be called. | ||
60 | /// If the installation was canceled or failed at any later point, this method will be called at the end. | ||
61 | /// <p> | ||
62 | /// Win32 MSI API: | ||
63 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/shutdownembeddedui.asp">ShutdownEmbeddedUI</a> | ||
64 | /// </p></remarks> | ||
65 | void Shutdown(); | ||
66 | } | ||
67 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs new file mode 100644 index 00000000..f29612d6 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs | |||
@@ -0,0 +1,67 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System.Diagnostics.CodeAnalysis; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Represents a per-drive disk space cost for an installation. | ||
9 | /// </summary> | ||
10 | [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] | ||
11 | public struct InstallCost | ||
12 | { | ||
13 | private string driveName; | ||
14 | private long cost; | ||
15 | private long tempCost; | ||
16 | |||
17 | /// <summary> | ||
18 | /// Creates a new InstallCost object. | ||
19 | /// </summary> | ||
20 | /// <param name="driveName">name of the drive this cost data applies to</param> | ||
21 | /// <param name="cost">installation cost on this drive, as a number of bytes</param> | ||
22 | /// <param name="tempCost">temporary disk space required on this drive, as a number of bytes</param> | ||
23 | internal InstallCost(string driveName, long cost, long tempCost) | ||
24 | { | ||
25 | this.driveName = driveName; | ||
26 | this.cost = cost; | ||
27 | this.tempCost = tempCost; | ||
28 | } | ||
29 | |||
30 | /// <summary> | ||
31 | /// The name of the drive this cost data applies to. | ||
32 | /// </summary> | ||
33 | public string DriveName | ||
34 | { | ||
35 | get | ||
36 | { | ||
37 | return this.driveName; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// The installation cost on this drive, as a number of bytes. | ||
43 | /// </summary> | ||
44 | public long Cost | ||
45 | { | ||
46 | get | ||
47 | { | ||
48 | return this.cost; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | /// <summary> | ||
53 | /// The temporary disk space required on this drive, as a number of bytes. | ||
54 | /// </summary> | ||
55 | /// <remarks><p> | ||
56 | /// This temporary space requirement is space needed only for the duration | ||
57 | /// of the installation, over the final footprint on disk. | ||
58 | /// </p></remarks> | ||
59 | public long TempCost | ||
60 | { | ||
61 | get | ||
62 | { | ||
63 | return this.tempCost; | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs new file mode 100644 index 00000000..47ca00a1 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs | |||
@@ -0,0 +1,100 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Globalization; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Subclasses of this abstract class represent a unique instance of a | ||
11 | /// registered product or patch installation. | ||
12 | /// </summary> | ||
13 | public abstract class Installation | ||
14 | { | ||
15 | private string installationCode; | ||
16 | private string userSid; | ||
17 | private UserContexts context; | ||
18 | private SourceList sourceList; | ||
19 | |||
20 | internal Installation(string installationCode, string userSid, UserContexts context) | ||
21 | { | ||
22 | if (context == UserContexts.Machine) | ||
23 | { | ||
24 | userSid = null; | ||
25 | } | ||
26 | this.installationCode = installationCode; | ||
27 | this.userSid = userSid; | ||
28 | this.context = context; | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Gets the user security identifier (SID) under which this product or patch | ||
33 | /// installation is available. | ||
34 | /// </summary> | ||
35 | public string UserSid | ||
36 | { | ||
37 | get | ||
38 | { | ||
39 | return this.userSid; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | /// <summary> | ||
44 | /// Gets the user context of this product or patch installation. | ||
45 | /// </summary> | ||
46 | public UserContexts Context | ||
47 | { | ||
48 | get | ||
49 | { | ||
50 | return this.context; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | /// <summary> | ||
55 | /// Gets the source list of this product or patch installation. | ||
56 | /// </summary> | ||
57 | public virtual SourceList SourceList | ||
58 | { | ||
59 | get | ||
60 | { | ||
61 | if (this.sourceList == null) | ||
62 | { | ||
63 | this.sourceList = new SourceList(this); | ||
64 | } | ||
65 | return this.sourceList; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Gets a value indicating whether this product or patch is installed on the current system. | ||
71 | /// </summary> | ||
72 | public abstract bool IsInstalled | ||
73 | { | ||
74 | get; | ||
75 | } | ||
76 | |||
77 | internal string InstallationCode | ||
78 | { | ||
79 | get | ||
80 | { | ||
81 | return this.installationCode; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | internal abstract int InstallationType | ||
86 | { | ||
87 | get; | ||
88 | } | ||
89 | |||
90 | /// <summary> | ||
91 | /// Gets a property about the product or patch installation. | ||
92 | /// </summary> | ||
93 | /// <param name="propertyName">Name of the property being retrieved.</param> | ||
94 | /// <returns></returns> | ||
95 | public abstract string this[string propertyName] | ||
96 | { | ||
97 | get; | ||
98 | } | ||
99 | } | ||
100 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs new file mode 100644 index 00000000..ce5a6a94 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs | |||
@@ -0,0 +1,82 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | /// <summary> | ||
6 | /// Subclasses of this abstract class represent an instance | ||
7 | /// of a registered feature or component. | ||
8 | /// </summary> | ||
9 | public abstract class InstallationPart | ||
10 | { | ||
11 | private string id; | ||
12 | private string productCode; | ||
13 | private string userSid; | ||
14 | private UserContexts context; | ||
15 | |||
16 | internal InstallationPart(string id, string productCode) | ||
17 | : this(id, productCode, null, UserContexts.None) | ||
18 | { | ||
19 | } | ||
20 | |||
21 | internal InstallationPart(string id, string productCode, string userSid, UserContexts context) | ||
22 | { | ||
23 | this.id = id; | ||
24 | this.productCode = productCode; | ||
25 | this.userSid = userSid; | ||
26 | this.context = context; | ||
27 | } | ||
28 | |||
29 | internal string Id | ||
30 | { | ||
31 | get | ||
32 | { | ||
33 | return this.id; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | internal string ProductCode | ||
38 | { | ||
39 | get | ||
40 | { | ||
41 | return this.productCode; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | internal string UserSid | ||
46 | { | ||
47 | get | ||
48 | { | ||
49 | return this.userSid; | ||
50 | } | ||
51 | } | ||
52 | |||
53 | internal UserContexts Context | ||
54 | { | ||
55 | get | ||
56 | { | ||
57 | return this.context; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Gets the product that this item is a part of. | ||
63 | /// </summary> | ||
64 | public ProductInstallation Product | ||
65 | { | ||
66 | get | ||
67 | { | ||
68 | return this.productCode != null ? | ||
69 | new ProductInstallation(this.productCode, userSid, context) : null; | ||
70 | } | ||
71 | } | ||
72 | |||
73 | /// <summary> | ||
74 | /// Gets the current installation state of the item. | ||
75 | /// </summary> | ||
76 | public abstract InstallState State | ||
77 | { | ||
78 | get; | ||
79 | } | ||
80 | } | ||
81 | |||
82 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs new file mode 100644 index 00000000..8df0aed9 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs | |||
@@ -0,0 +1,890 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Resources; | ||
9 | using System.Reflection; | ||
10 | using System.Collections.Generic; | ||
11 | using System.Globalization; | ||
12 | using System.Runtime.InteropServices; | ||
13 | using System.Diagnostics.CodeAnalysis; | ||
14 | |||
15 | /// <summary> | ||
16 | /// Receives an exception from | ||
17 | /// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/> | ||
18 | /// indicating the reason a particular patch is not applicable to a product. | ||
19 | /// </summary> | ||
20 | /// <param name="patch">MSP file path, XML file path, or XML blob that was passed to | ||
21 | /// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/></param> | ||
22 | /// <param name="exception">exception indicating the reason the patch is not applicable</param> | ||
23 | /// <remarks><p> | ||
24 | /// If <paramref name="exception"/> is an <see cref="InstallerException"/> or subclass, then | ||
25 | /// its <see cref="InstallerException.ErrorCode"/> and <see cref="InstallerException.Message"/> | ||
26 | /// properties will indicate a more specific reason the patch was not applicable. | ||
27 | /// </p><p> | ||
28 | /// The <paramref name="exception"/> could also be a FileNotFoundException if the | ||
29 | /// patch string was a file path. | ||
30 | /// </p></remarks> | ||
31 | public delegate void InapplicablePatchHandler(string patch, Exception exception); | ||
32 | |||
33 | /// <summary> | ||
34 | /// Provides static methods for installing and configuring products and patches. | ||
35 | /// </summary> | ||
36 | public static partial class Installer | ||
37 | { | ||
38 | private static bool rebootRequired; | ||
39 | private static bool rebootInitiated; | ||
40 | private static ResourceManager errorResources; | ||
41 | |||
42 | /// <summary> | ||
43 | /// Indicates whether a system reboot is required after running an installation or configuration operation. | ||
44 | /// </summary> | ||
45 | public static bool RebootRequired | ||
46 | { | ||
47 | get | ||
48 | { | ||
49 | return Installer.rebootRequired; | ||
50 | } | ||
51 | } | ||
52 | |||
53 | /// <summary> | ||
54 | /// Indicates whether a system reboot has been initiated after running an installation or configuration operation. | ||
55 | /// </summary> | ||
56 | public static bool RebootInitiated | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | return Installer.rebootInitiated; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Enables the installer's internal user interface. Then this user interface is used | ||
66 | /// for all subsequent calls to user-interface-generating installer functions in this process. | ||
67 | /// </summary> | ||
68 | /// <param name="uiOptions">Specifies the level of complexity of the user interface</param> | ||
69 | /// <param name="windowHandle">Handle to a window, which becomes the owner of any user interface created. | ||
70 | /// A pointer to the previous owner of the user interface is returned.</param> | ||
71 | /// <returns>The previous user interface level</returns> | ||
72 | /// <remarks><p> | ||
73 | /// Win32 MSI API: | ||
74 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a> | ||
75 | /// </p></remarks> | ||
76 | [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] | ||
77 | public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions, ref IntPtr windowHandle) | ||
78 | { | ||
79 | return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, ref windowHandle); | ||
80 | } | ||
81 | |||
82 | /// <summary> | ||
83 | /// Enables the installer's internal user interface. Then this user interface is used | ||
84 | /// for all subsequent calls to user-interface-generating installer functions in this process. | ||
85 | /// The owner of the user interface does not change. | ||
86 | /// </summary> | ||
87 | /// <param name="uiOptions">Specifies the level of complexity of the user interface</param> | ||
88 | /// <returns>The previous user interface level</returns> | ||
89 | /// <remarks><p> | ||
90 | /// Win32 MSI API: | ||
91 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a> | ||
92 | /// </p></remarks> | ||
93 | public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions) | ||
94 | { | ||
95 | return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, IntPtr.Zero); | ||
96 | } | ||
97 | |||
98 | /// <summary> | ||
99 | /// Enables logging of the selected message type for all subsequent install sessions in | ||
100 | /// the current process space. | ||
101 | /// </summary> | ||
102 | /// <param name="logModes">One or more mode flags specifying the type of messages to log</param> | ||
103 | /// <param name="logFile">Full path to the log file. A null path disables logging, | ||
104 | /// in which case the logModes paraneter is ignored.</param> | ||
105 | /// <exception cref="ArgumentException">an invalid log mode was specified</exception> | ||
106 | /// <remarks>This method takes effect on any new installation processes. Calling this | ||
107 | /// method from within a custom action will not start logging for that installation.</remarks> | ||
108 | public static void EnableLog(InstallLogModes logModes, string logFile) | ||
109 | { | ||
110 | Installer.EnableLog(logModes, logFile, false, true); | ||
111 | } | ||
112 | |||
113 | /// <summary> | ||
114 | /// Enables logging of the selected message type for all subsequent install sessions in | ||
115 | /// the current process space. | ||
116 | /// </summary> | ||
117 | /// <param name="logModes">One or more mode flags specifying the type of messages to log</param> | ||
118 | /// <param name="logFile">Full path to the log file. A null path disables logging, | ||
119 | /// in which case the logModes paraneter is ignored.</param> | ||
120 | /// <param name="append">If true, the log lines will be appended to any existing file content. | ||
121 | /// If false, the log file will be truncated if it exists. The default is false.</param> | ||
122 | /// <param name="flushEveryLine">If true, the log will be flushed after every line. | ||
123 | /// If false, the log will be flushed every 20 lines. The default is true.</param> | ||
124 | /// <exception cref="ArgumentException">an invalid log mode was specified</exception> | ||
125 | /// <remarks><p> | ||
126 | /// This method takes effect on any new installation processes. Calling this | ||
127 | /// method from within a custom action will not start logging for that installation. | ||
128 | /// </p><p> | ||
129 | /// Win32 MSI API: | ||
130 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienablelog.asp">MsiEnableLog</a> | ||
131 | /// </p></remarks> | ||
132 | public static void EnableLog(InstallLogModes logModes, string logFile, bool append, bool flushEveryLine) | ||
133 | { | ||
134 | uint ret = NativeMethods.MsiEnableLog((uint) logModes, logFile, (append ? (uint) 1 : 0) + (flushEveryLine ? (uint) 2 : 0)); | ||
135 | if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID) | ||
136 | { | ||
137 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | /// <summary> | ||
142 | /// increments the usage count for a particular feature and returns the installation state for | ||
143 | /// that feature. This method should be used to indicate an application's intent to use a feature. | ||
144 | /// </summary> | ||
145 | /// <param name="productCode">The product code of the product.</param> | ||
146 | /// <param name="feature">The feature to be used.</param> | ||
147 | /// <param name="installMode">Must have the value <see cref="InstallMode.NoDetection"/>.</param> | ||
148 | /// <returns>The installed state of the feature.</returns> | ||
149 | /// <remarks><p> | ||
150 | /// The UseFeature method should only be used on features known to be published. The application | ||
151 | /// should determine the status of the feature by calling either the FeatureState method or | ||
152 | /// Features method. | ||
153 | /// </p><p> | ||
154 | /// Win32 MSI APIs: | ||
155 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeature.asp">MsiUseFeature</a>, | ||
156 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeatureex.asp">MsiUseFeatureEx</a> | ||
157 | /// </p></remarks> | ||
158 | public static InstallState UseFeature(string productCode, string feature, InstallMode installMode) | ||
159 | { | ||
160 | int installState = NativeMethods.MsiUseFeatureEx(productCode, feature, unchecked ((uint) installMode), 0); | ||
161 | return (InstallState) installState; | ||
162 | } | ||
163 | |||
164 | /// <summary> | ||
165 | /// Opens an installer package for use with functions that access the product database and install engine, | ||
166 | /// returning an Session object. | ||
167 | /// </summary> | ||
168 | /// <param name="packagePath">Path to the package</param> | ||
169 | /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the | ||
170 | /// computer state and that is incapable of changing the current computer state. A value of false yields | ||
171 | /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current | ||
172 | /// machine state.</param> | ||
173 | /// <returns>A Session object allowing access to the product database and install engine</returns> | ||
174 | /// <exception cref="InstallerException">The product could not be opened</exception> | ||
175 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
176 | /// <remarks><p> | ||
177 | /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a | ||
178 | /// custom action because the active installation is the only session allowed. | ||
179 | /// </p><p> | ||
180 | /// A "safe" Session object ignores the current computer state when opening the package and prevents | ||
181 | /// changes to the current computer state. | ||
182 | /// </p><p> | ||
183 | /// The Session object should be <see cref="InstallerHandle.Close"/>d after use. | ||
184 | /// It is best that the handle be closed manually as soon as it is no longer | ||
185 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
186 | /// </p><p> | ||
187 | /// Win32 MSI APIs: | ||
188 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>, | ||
189 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a> | ||
190 | /// </p></remarks> | ||
191 | public static Session OpenPackage(string packagePath, bool ignoreMachineState) | ||
192 | { | ||
193 | int sessionHandle; | ||
194 | uint ret = NativeMethods.MsiOpenPackageEx(packagePath, ignoreMachineState ? (uint) 1 : 0, out sessionHandle); | ||
195 | if (ret != 0) | ||
196 | { | ||
197 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
198 | } | ||
199 | return new Session((IntPtr) sessionHandle, true); | ||
200 | } | ||
201 | |||
202 | /// <summary> | ||
203 | /// Opens an installer package for use with functions that access the product database and install engine, | ||
204 | /// returning an Session object. | ||
205 | /// </summary> | ||
206 | /// <param name="database">Database used to create the session</param> | ||
207 | /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the | ||
208 | /// computer state and that is incapable of changing the current computer state. A value of false yields | ||
209 | /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current | ||
210 | /// machine state.</param> | ||
211 | /// <returns>A Session object allowing access to the product database and install engine</returns> | ||
212 | /// <exception cref="InstallerException">The product could not be opened</exception> | ||
213 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
214 | /// <remarks><p> | ||
215 | /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a | ||
216 | /// custom action because the active installation is the only session allowed. | ||
217 | /// </p><p> | ||
218 | /// A "safe" Session object ignores the current computer state when opening the package and prevents | ||
219 | /// changes to the current computer state. | ||
220 | /// </p><p> | ||
221 | /// The Session object should be <see cref="InstallerHandle.Close"/>d after use. | ||
222 | /// It is best that the handle be closed manually as soon as it is no longer | ||
223 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
224 | /// </p><p> | ||
225 | /// Win32 MSI APIs: | ||
226 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>, | ||
227 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a> | ||
228 | /// </p></remarks> | ||
229 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
230 | public static Session OpenPackage(Database database, bool ignoreMachineState) | ||
231 | { | ||
232 | if (database == null) | ||
233 | { | ||
234 | throw new ArgumentNullException("database"); | ||
235 | } | ||
236 | |||
237 | return Installer.OpenPackage( | ||
238 | String.Format(CultureInfo.InvariantCulture, "#{0}", database.Handle), | ||
239 | ignoreMachineState); | ||
240 | } | ||
241 | |||
242 | /// <summary> | ||
243 | /// Opens an installer package for an installed product using the product code. | ||
244 | /// </summary> | ||
245 | /// <param name="productCode">Product code of the installed product</param> | ||
246 | /// <returns>A Session object allowing access to the product database and install engine, | ||
247 | /// or null if the specified product is not installed.</returns> | ||
248 | /// <exception cref="ArgumentException">An unknown product was requested</exception> | ||
249 | /// <exception cref="InstallerException">The product could not be opened</exception> | ||
250 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
251 | /// <remarks><p> | ||
252 | /// Note that only one Session object can be opened by a single process. OpenProduct cannot be | ||
253 | /// used in a custom action because the active installation is the only session allowed. | ||
254 | /// </p><p> | ||
255 | /// The Session object should be <see cref="InstallerHandle.Close"/>d after use. | ||
256 | /// It is best that the handle be closed manually as soon as it is no longer | ||
257 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
258 | /// </p><p> | ||
259 | /// Win32 MSI API: | ||
260 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenproduct.asp">MsiOpenProduct</a> | ||
261 | /// </p></remarks> | ||
262 | public static Session OpenProduct(string productCode) | ||
263 | { | ||
264 | int sessionHandle; | ||
265 | uint ret = NativeMethods.MsiOpenProduct(productCode, out sessionHandle); | ||
266 | if (ret != 0) | ||
267 | { | ||
268 | if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT) | ||
269 | { | ||
270 | return null; | ||
271 | } | ||
272 | else | ||
273 | { | ||
274 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
275 | } | ||
276 | } | ||
277 | return new Session((IntPtr) sessionHandle, true); | ||
278 | } | ||
279 | |||
280 | /// <summary> | ||
281 | /// Gets the full component path, performing any necessary installation. This method prompts for source if | ||
282 | /// necessary and increments the usage count for the feature. | ||
283 | /// </summary> | ||
284 | /// <param name="product">Product code for the product that contains the feature with the necessary component</param> | ||
285 | /// <param name="feature">Feature ID of the feature with the necessary component</param> | ||
286 | /// <param name="component">Component code of the necessary component</param> | ||
287 | /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param> | ||
288 | /// <returns>Path to the component</returns> | ||
289 | /// <remarks><p> | ||
290 | /// Win32 MSI API: | ||
291 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidecomponent.asp">MsiProvideComponent</a> | ||
292 | /// </p></remarks> | ||
293 | public static string ProvideComponent(string product, string feature, string component, InstallMode installMode) | ||
294 | { | ||
295 | StringBuilder pathBuf = new StringBuilder(512); | ||
296 | uint pathBufSize = (uint) pathBuf.Capacity; | ||
297 | uint ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize); | ||
298 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
299 | { | ||
300 | pathBuf.Capacity = (int) ++pathBufSize; | ||
301 | ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize); | ||
302 | } | ||
303 | |||
304 | if (ret != 0) | ||
305 | { | ||
306 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
307 | } | ||
308 | return pathBuf.ToString(); | ||
309 | } | ||
310 | |||
311 | /// <summary> | ||
312 | /// Gets the full component path for a qualified component that is published by a product and | ||
313 | /// performs any necessary installation. This method prompts for source if necessary and increments | ||
314 | /// the usage count for the feature. | ||
315 | /// </summary> | ||
316 | /// <param name="component">Specifies the component ID for the requested component. This may not be the | ||
317 | /// GUID for the component itself but rather a server that provides the correct functionality, as in the | ||
318 | /// ComponentId column of the PublishComponent table.</param> | ||
319 | /// <param name="qualifier">Specifies a qualifier into a list of advertising components (from PublishComponent Table).</param> | ||
320 | /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param> | ||
321 | /// <param name="product">Optional; specifies the product to match that has published the qualified component.</param> | ||
322 | /// <returns>Path to the component</returns> | ||
323 | /// <remarks><p> | ||
324 | /// Win32 MSI APIs: | ||
325 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponent.asp">MsiProvideQualifiedComponent</a> | ||
326 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponentex.asp">MsiProvideQualifiedComponentEx</a> | ||
327 | /// </p></remarks> | ||
328 | public static string ProvideQualifiedComponent(string component, string qualifier, InstallMode installMode, string product) | ||
329 | { | ||
330 | StringBuilder pathBuf = new StringBuilder(512); | ||
331 | uint pathBufSize = (uint) pathBuf.Capacity; | ||
332 | uint ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize); | ||
333 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
334 | { | ||
335 | pathBuf.Capacity = (int) ++pathBufSize; | ||
336 | ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize); | ||
337 | } | ||
338 | |||
339 | if (ret != 0) | ||
340 | { | ||
341 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
342 | } | ||
343 | return pathBuf.ToString(); | ||
344 | } | ||
345 | |||
346 | /// <summary> | ||
347 | /// Gets the full path to a Windows Installer component containing an assembly. This method prompts for a source and | ||
348 | /// increments the usage count for the feature. | ||
349 | /// </summary> | ||
350 | /// <param name="assemblyName">Assembly name</param> | ||
351 | /// <param name="appContext">Set to null for global assemblies. For private assemblies, set to the full path of the | ||
352 | /// application configuration file (.cfg file) or executable file (.exe) of the application to which the assembly | ||
353 | /// has been made private.</param> | ||
354 | /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param> | ||
355 | /// <param name="isWin32Assembly">True if this is a Win32 assembly, false if it is a .NET assembly</param> | ||
356 | /// <returns>Path to the assembly</returns> | ||
357 | /// <remarks><p> | ||
358 | /// Win32 MSI API: | ||
359 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovideassembly.asp">MsiProvideAssembly</a> | ||
360 | /// </p></remarks> | ||
361 | public static string ProvideAssembly(string assemblyName, string appContext, InstallMode installMode, bool isWin32Assembly) | ||
362 | { | ||
363 | StringBuilder pathBuf = new StringBuilder(512); | ||
364 | uint pathBufSize = (uint) pathBuf.Capacity; | ||
365 | uint ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize); | ||
366 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
367 | { | ||
368 | pathBuf.Capacity = (int) ++pathBufSize; | ||
369 | ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize); | ||
370 | } | ||
371 | |||
372 | if (ret != 0) | ||
373 | { | ||
374 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
375 | } | ||
376 | return pathBuf.ToString(); | ||
377 | } | ||
378 | |||
379 | /// <summary> | ||
380 | /// Installs files that are unexpectedly missing. | ||
381 | /// </summary> | ||
382 | /// <param name="product">Product code for the product that owns the component to be installed</param> | ||
383 | /// <param name="component">Component to be installed</param> | ||
384 | /// <param name="installState">Specifies the way the component should be installed.</param> | ||
385 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
386 | /// <remarks><p> | ||
387 | /// Win32 MSI API: | ||
388 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingcomponent.asp">MsiInstallMissingComponent</a> | ||
389 | /// </p></remarks> | ||
390 | public static void InstallMissingComponent(string product, string component, InstallState installState) | ||
391 | { | ||
392 | uint ret = NativeMethods.MsiInstallMissingComponent(product, component, (int) installState); | ||
393 | if (ret != 0) | ||
394 | { | ||
395 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
396 | } | ||
397 | } | ||
398 | |||
399 | /// <summary> | ||
400 | /// Installs files that are unexpectedly missing. | ||
401 | /// </summary> | ||
402 | /// <param name="product">Product code for the product that owns the file to be installed</param> | ||
403 | /// <param name="file">File to be installed</param> | ||
404 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
405 | /// <remarks><p> | ||
406 | /// Win32 MSI API: | ||
407 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingfile.asp">MsiInstallMissingFile</a> | ||
408 | /// </p></remarks> | ||
409 | public static void InstallMissingFile(string product, string file) | ||
410 | { | ||
411 | uint ret = NativeMethods.MsiInstallMissingFile(product, file); | ||
412 | if (ret != 0) | ||
413 | { | ||
414 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
415 | } | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Reinstalls a feature. | ||
420 | /// </summary> | ||
421 | /// <param name="product">Product code for the product containing the feature to be reinstalled</param> | ||
422 | /// <param name="feature">Feature to be reinstalled</param> | ||
423 | /// <param name="reinstallModes">Reinstall modes</param> | ||
424 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
425 | /// <remarks><p> | ||
426 | /// Win32 MSI API: | ||
427 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallfeature.asp">MsiReinstallFeature</a> | ||
428 | /// </p></remarks> | ||
429 | public static void ReinstallFeature(string product, string feature, ReinstallModes reinstallModes) | ||
430 | { | ||
431 | uint ret = NativeMethods.MsiReinstallFeature(product, feature, (uint) reinstallModes); | ||
432 | if (ret != 0) | ||
433 | { | ||
434 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
435 | } | ||
436 | } | ||
437 | |||
438 | /// <summary> | ||
439 | /// Reinstalls a product. | ||
440 | /// </summary> | ||
441 | /// <param name="product">Product code for the product to be reinstalled</param> | ||
442 | /// <param name="reinstallModes">Reinstall modes</param> | ||
443 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
444 | /// <remarks><p> | ||
445 | /// Win32 MSI API: | ||
446 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallproduct.asp">MsiReinstallProduct</a> | ||
447 | /// </p></remarks> | ||
448 | public static void ReinstallProduct(string product, ReinstallModes reinstallModes) | ||
449 | { | ||
450 | uint ret = NativeMethods.MsiReinstallProduct(product, (uint) reinstallModes); | ||
451 | if (ret != 0) | ||
452 | { | ||
453 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | /// <summary> | ||
458 | /// Opens an installer package and initializes an install session. | ||
459 | /// </summary> | ||
460 | /// <param name="packagePath">path to the patch package</param> | ||
461 | /// <param name="commandLine">command line property settings</param> | ||
462 | /// <exception cref="InstallerException">There was an error installing the product</exception> | ||
463 | /// <remarks><p> | ||
464 | /// To completely remove a product, set REMOVE=ALL in <paramRef name="commandLine"/>. | ||
465 | /// </p><p> | ||
466 | /// This method displays the user interface with the current settings and | ||
467 | /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/> | ||
468 | /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the | ||
469 | /// <see cref="EnableLog(InstallLogModes,string)"/> function. | ||
470 | /// </p><p> | ||
471 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
472 | /// tested after calling this method. | ||
473 | /// </p><p> | ||
474 | /// Win32 MSI API: | ||
475 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallproduct.asp">MsiInstallProduct</a> | ||
476 | /// </p></remarks> | ||
477 | public static void InstallProduct(string packagePath, string commandLine) | ||
478 | { | ||
479 | uint ret = NativeMethods.MsiInstallProduct(packagePath, commandLine); | ||
480 | Installer.CheckInstallResult(ret); | ||
481 | } | ||
482 | |||
483 | /// <summary> | ||
484 | /// Installs or uninstalls a product. | ||
485 | /// </summary> | ||
486 | /// <param name="productCode">Product code of the product to be configured.</param> | ||
487 | /// <param name="installLevel">Specifies the default installation configuration of the | ||
488 | /// product. The <paramref name="installLevel"/> parameter is ignored and all features | ||
489 | /// are installed if the <paramref name="installState"/> parameter is set to any other | ||
490 | /// value than <see cref="InstallState.Default"/>. This parameter must be either 0 | ||
491 | /// (install using authored feature levels), 65535 (install all features), or a value | ||
492 | /// between 0 and 65535 to install a subset of available features. </param> | ||
493 | /// <param name="installState">Specifies the installation state for the product.</param> | ||
494 | /// <param name="commandLine">Specifies the command line property settings. This should | ||
495 | /// be a list of the format Property=Setting Property=Setting.</param> | ||
496 | /// <exception cref="InstallerException">There was an error configuring the product</exception> | ||
497 | /// <remarks><p> | ||
498 | /// This method displays the user interface with the current settings and | ||
499 | /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/> | ||
500 | /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the | ||
501 | /// <see cref="EnableLog(InstallLogModes,string)"/> function. | ||
502 | /// </p><p> | ||
503 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
504 | /// tested after calling this method. | ||
505 | /// </p><p> | ||
506 | /// Win32 MSI APIs: | ||
507 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproduct.asp">MsiConfigureProduct</a>, | ||
508 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproductex.asp">MsiConfigureProductEx</a> | ||
509 | /// </p></remarks> | ||
510 | public static void ConfigureProduct(string productCode, int installLevel, InstallState installState, string commandLine) | ||
511 | { | ||
512 | uint ret = NativeMethods.MsiConfigureProductEx(productCode, installLevel, (int) installState, commandLine); | ||
513 | Installer.CheckInstallResult(ret); | ||
514 | } | ||
515 | |||
516 | /// <summary> | ||
517 | /// Configures the installed state for a product feature. | ||
518 | /// </summary> | ||
519 | /// <param name="productCode">Product code of the product to be configured.</param> | ||
520 | /// <param name="feature">Specifies the feature ID for the feature to be configured.</param> | ||
521 | /// <param name="installState">Specifies the installation state for the feature.</param> | ||
522 | /// <exception cref="InstallerException">There was an error configuring the feature</exception> | ||
523 | /// <remarks><p> | ||
524 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
525 | /// tested after calling this method. | ||
526 | /// </p><p> | ||
527 | /// Win32 MSI API: | ||
528 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigurefeature.asp">MsiConfigureFeature</a> | ||
529 | /// </p></remarks> | ||
530 | public static void ConfigureFeature(string productCode, string feature, InstallState installState) | ||
531 | { | ||
532 | uint ret = NativeMethods.MsiConfigureFeature(productCode, feature, (int) installState); | ||
533 | Installer.CheckInstallResult(ret); | ||
534 | } | ||
535 | |||
536 | /// <summary> | ||
537 | /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes | ||
538 | /// an installation and sets the PATCH property to the path of the patch package. | ||
539 | /// </summary> | ||
540 | /// <param name="patchPackage">path to the patch package</param> | ||
541 | /// <param name="commandLine">optional command line property settings</param> | ||
542 | /// <exception cref="InstallerException">There was an error applying the patch</exception> | ||
543 | /// <remarks><p> | ||
544 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
545 | /// tested after calling this method. | ||
546 | /// </p><p> | ||
547 | /// Win32 MSI API: | ||
548 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a> | ||
549 | /// </p></remarks> | ||
550 | public static void ApplyPatch(string patchPackage, string commandLine) | ||
551 | { | ||
552 | Installer.ApplyPatch(patchPackage, null, InstallType.Default, commandLine); | ||
553 | } | ||
554 | |||
555 | /// <summary> | ||
556 | /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes | ||
557 | /// an installation and sets the PATCH property to the path of the patch package. | ||
558 | /// </summary> | ||
559 | /// <param name="patchPackage">path to the patch package</param> | ||
560 | /// <param name="installPackage">path to the product to be patched, if installType | ||
561 | /// is set to <see cref="InstallType.NetworkImage"/></param> | ||
562 | /// <param name="installType">type of installation to patch</param> | ||
563 | /// <param name="commandLine">optional command line property settings</param> | ||
564 | /// <exception cref="InstallerException">There was an error applying the patch</exception> | ||
565 | /// <remarks><p> | ||
566 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
567 | /// tested after calling this method. | ||
568 | /// </p><p> | ||
569 | /// Win32 MSI API: | ||
570 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a> | ||
571 | /// </p></remarks> | ||
572 | public static void ApplyPatch(string patchPackage, string installPackage, InstallType installType, string commandLine) | ||
573 | { | ||
574 | uint ret = NativeMethods.MsiApplyPatch(patchPackage, installPackage, (int) installType, commandLine); | ||
575 | Installer.CheckInstallResult(ret); | ||
576 | } | ||
577 | |||
578 | /// <summary> | ||
579 | /// Removes one or more patches from a single product. To remove a patch from | ||
580 | /// multiple products, RemovePatches must be called for each product. | ||
581 | /// </summary> | ||
582 | /// <param name="patches">List of patches to remove. Each patch can be specified by the GUID | ||
583 | /// of the patch or the full path to the patch package.</param> | ||
584 | /// <param name="productCode">The ProductCode (GUID) of the product from which the patches | ||
585 | /// are removed. This parameter cannot be null.</param> | ||
586 | /// <param name="commandLine">optional command line property settings</param> | ||
587 | /// <exception cref="InstallerException">There was an error removing the patches</exception> | ||
588 | /// <remarks><p> | ||
589 | /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be | ||
590 | /// tested after calling this method. | ||
591 | /// </p><p> | ||
592 | /// Win32 MSI API: | ||
593 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiremovepatches.asp">MsiRemovePatches</a> | ||
594 | /// </p></remarks> | ||
595 | public static void RemovePatches(IList<string> patches, string productCode, string commandLine) | ||
596 | { | ||
597 | if (patches == null || patches.Count == 0) | ||
598 | { | ||
599 | throw new ArgumentNullException("patches"); | ||
600 | } | ||
601 | |||
602 | if (productCode == null) | ||
603 | { | ||
604 | throw new ArgumentNullException("productCode"); | ||
605 | } | ||
606 | |||
607 | StringBuilder patchList = new StringBuilder(); | ||
608 | foreach (string patch in patches) | ||
609 | { | ||
610 | if (patch != null) | ||
611 | { | ||
612 | if (patchList.Length != 0) | ||
613 | { | ||
614 | patchList.Append(';'); | ||
615 | } | ||
616 | |||
617 | patchList.Append(patch); | ||
618 | } | ||
619 | } | ||
620 | |||
621 | if (patchList.Length == 0) | ||
622 | { | ||
623 | throw new ArgumentNullException("patches"); | ||
624 | } | ||
625 | |||
626 | uint ret = NativeMethods.MsiRemovePatches(patchList.ToString(), productCode, (int) InstallType.SingleInstance, commandLine); | ||
627 | Installer.CheckInstallResult(ret); | ||
628 | } | ||
629 | |||
630 | /// <summary> | ||
631 | /// Determines which patches apply to a specified product MSI and in what sequence. | ||
632 | /// </summary> | ||
633 | /// <param name="productPackage">Full path to an MSI file that is the target product | ||
634 | /// for the set of patches.</param> | ||
635 | /// <param name="patches">An array of strings specifying the patches to be checked. Each item | ||
636 | /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param> | ||
637 | /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the | ||
638 | /// reason the patch is not applicable. This value may be left null if that information is not | ||
639 | /// desired.</param> | ||
640 | /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating | ||
641 | /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns> | ||
642 | /// <remarks><p> | ||
643 | /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML, | ||
644 | /// it is assumed to be an MSP file. | ||
645 | /// </p><p> | ||
646 | /// As this overload uses InstallContext.None, it does not consider the current state of | ||
647 | /// the system. | ||
648 | /// </p><p> | ||
649 | /// Win32 MSI API: | ||
650 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a> | ||
651 | /// </p></remarks> | ||
652 | public static IList<string> DetermineApplicablePatches( | ||
653 | string productPackage, | ||
654 | string[] patches, | ||
655 | InapplicablePatchHandler errorHandler) | ||
656 | { | ||
657 | return DetermineApplicablePatches(productPackage, patches, errorHandler, null, UserContexts.None); | ||
658 | } | ||
659 | |||
660 | /// <summary> | ||
661 | /// Determines which patches apply to a specified product and in what sequence. If | ||
662 | /// the product is installed, this method accounts for patches that have already been applied to | ||
663 | /// the product and accounts for obsolete and superceded patches. | ||
664 | /// </summary> | ||
665 | /// <param name="product">The product that is the target for the set of patches. This may be | ||
666 | /// either a ProductCode (GUID) of a product that is currently installed, or the path to a an | ||
667 | /// MSI file.</param> | ||
668 | /// <param name="patches">An array of strings specifying the patches to be checked. Each item | ||
669 | /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param> | ||
670 | /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the | ||
671 | /// reason the patch is not applicable. This value may be left null if that information is not | ||
672 | /// desired.</param> | ||
673 | /// <param name="userSid">Specifies a security identifier (SID) of a user. This parameter restricts | ||
674 | /// the context of enumeration for this user account. This parameter cannot be the special SID | ||
675 | /// strings s-1-1-0 (everyone) or s-1-5-18 (local system). If <paramref name="context"/> is set to | ||
676 | /// <see cref="UserContexts.None"/> or <see cref="UserContexts.Machine"/>, then | ||
677 | /// <paramref name="userSid"/> must be null. For the current user context, <paramref name="userSid"/> | ||
678 | /// can be null and <paramref name="context"/> can be set to <see cref="UserContexts.UserManaged"/> | ||
679 | /// or <see cref="UserContexts.UserUnmanaged"/>.</param> | ||
680 | /// <param name="context">Restricts the enumeration to per-user-unmanaged, per-user-managed, | ||
681 | /// or per-machine context, or (if referring to an MSI) to no system context at all. This | ||
682 | /// parameter can be <see cref="UserContexts.Machine"/>, <see cref="UserContexts.UserManaged"/>, | ||
683 | /// <see cref="UserContexts.UserUnmanaged"/>, or <see cref="UserContexts.None"/>.</param> | ||
684 | /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating | ||
685 | /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns> | ||
686 | /// <remarks><p> | ||
687 | /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML, | ||
688 | /// it is assumed to be an MSP file. | ||
689 | /// </p><p> | ||
690 | /// Passing an InstallContext of None only analyzes the MSI file; it does not consider the | ||
691 | /// current state of the system. You cannot use InstallContext.None with a ProductCode GUID. | ||
692 | /// </p><p> | ||
693 | /// Win32 MSI APIs: | ||
694 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a> | ||
695 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msideterminepatchsequence.asp">MsiDeterminePatchSequence</a> | ||
696 | /// </p></remarks> | ||
697 | public static IList<string> DetermineApplicablePatches( | ||
698 | string product, | ||
699 | string[] patches, | ||
700 | InapplicablePatchHandler errorHandler, | ||
701 | string userSid, | ||
702 | UserContexts context) | ||
703 | { | ||
704 | if (String.IsNullOrEmpty(product)) | ||
705 | { | ||
706 | throw new ArgumentNullException("product"); | ||
707 | } | ||
708 | |||
709 | if (patches == null) | ||
710 | { | ||
711 | throw new ArgumentNullException("patches"); | ||
712 | } | ||
713 | |||
714 | NativeMethods.MsiPatchSequenceData[] sequenceData = new NativeMethods.MsiPatchSequenceData[patches.Length]; | ||
715 | for (int i = 0; i < patches.Length; i++) | ||
716 | { | ||
717 | if (String.IsNullOrEmpty(patches[i])) | ||
718 | { | ||
719 | throw new ArgumentNullException("patches[" + i + "]"); | ||
720 | } | ||
721 | |||
722 | sequenceData[i].szPatchData = patches[i]; | ||
723 | sequenceData[i].ePatchDataType = GetPatchStringDataType(patches[i]); | ||
724 | sequenceData[i].dwOrder = -1; | ||
725 | sequenceData[i].dwStatus = 0; | ||
726 | } | ||
727 | |||
728 | uint ret; | ||
729 | if (context == UserContexts.None) | ||
730 | { | ||
731 | ret = NativeMethods.MsiDetermineApplicablePatches(product, (uint) sequenceData.Length, sequenceData); | ||
732 | } | ||
733 | else | ||
734 | { | ||
735 | ret = NativeMethods.MsiDeterminePatchSequence(product, userSid, context, (uint) sequenceData.Length, sequenceData); | ||
736 | } | ||
737 | |||
738 | if (errorHandler != null) | ||
739 | { | ||
740 | for (int i = 0; i < sequenceData.Length; i++) | ||
741 | { | ||
742 | if (sequenceData[i].dwOrder < 0 && sequenceData[i].dwStatus != 0) | ||
743 | { | ||
744 | errorHandler(sequenceData[i].szPatchData, InstallerException.ExceptionFromReturnCode(sequenceData[i].dwStatus)); | ||
745 | } | ||
746 | } | ||
747 | } | ||
748 | |||
749 | if (ret != 0) | ||
750 | { | ||
751 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
752 | } | ||
753 | |||
754 | IList<string> patchSeq = new List<string>(patches.Length); | ||
755 | for (int i = 0; i < sequenceData.Length; i++) | ||
756 | { | ||
757 | for (int j = 0; j < sequenceData.Length; j++) | ||
758 | { | ||
759 | if (sequenceData[j].dwOrder == i) | ||
760 | { | ||
761 | patchSeq.Add(sequenceData[j].szPatchData); | ||
762 | } | ||
763 | } | ||
764 | } | ||
765 | return patchSeq; | ||
766 | } | ||
767 | |||
768 | /// <summary> | ||
769 | /// Applies one or more patches to products that are eligible to receive the patch. | ||
770 | /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes | ||
771 | /// an installation and sets the PATCH property to the path of the patch package. | ||
772 | /// </summary> | ||
773 | /// <param name="patchPackages">The set of patch packages to be applied. | ||
774 | /// Each item is the full path to an MSP file.</param> | ||
775 | /// <param name="productCode">Provides the ProductCode of the product being patched. If this parameter | ||
776 | /// is null, the patches are applied to all products that are eligible to receive these patches.</param> | ||
777 | /// <param name="commandLine">optional command line property settings</param> | ||
778 | /// <remarks><p> | ||
779 | /// Win32 MSI API: | ||
780 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplymultiplepatches.asp">MsiApplyMultiplePatches</a> | ||
781 | /// </p></remarks> | ||
782 | public static void ApplyMultiplePatches( | ||
783 | IList<string> patchPackages, string productCode, string commandLine) | ||
784 | { | ||
785 | if (patchPackages == null || patchPackages.Count == 0) | ||
786 | { | ||
787 | throw new ArgumentNullException("patchPackages"); | ||
788 | } | ||
789 | |||
790 | StringBuilder patchList = new StringBuilder(); | ||
791 | foreach (string patch in patchPackages) | ||
792 | { | ||
793 | if (patch != null) | ||
794 | { | ||
795 | if (patchList.Length != 0) | ||
796 | { | ||
797 | patchList.Append(';'); | ||
798 | } | ||
799 | |||
800 | patchList.Append(patch); | ||
801 | } | ||
802 | } | ||
803 | |||
804 | if (patchList.Length == 0) | ||
805 | { | ||
806 | throw new ArgumentNullException("patchPackages"); | ||
807 | } | ||
808 | |||
809 | uint ret = NativeMethods.MsiApplyMultiplePatches(patchList.ToString(), productCode, commandLine); | ||
810 | Installer.CheckInstallResult(ret); | ||
811 | } | ||
812 | |||
813 | /// <summary> | ||
814 | /// Extracts information from a patch that can be used to determine whether the patch | ||
815 | /// applies on a target system. The method returns an XML string that can be provided to | ||
816 | /// <see cref="DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/> | ||
817 | /// instead of the full patch file. | ||
818 | /// </summary> | ||
819 | /// <param name="patchPath">Full path to the patch being queried.</param> | ||
820 | /// <returns>XML string containing patch data.</returns> | ||
821 | /// <remarks><p> | ||
822 | /// Win32 MSI API: | ||
823 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiextractpatchxmldata.asp">MsiExtractPatchXMLData</a> | ||
824 | /// </p></remarks> | ||
825 | public static string ExtractPatchXmlData(string patchPath) | ||
826 | { | ||
827 | StringBuilder buf = new StringBuilder(""); | ||
828 | uint bufSize = 0; | ||
829 | uint ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize); | ||
830 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
831 | { | ||
832 | buf.Capacity = (int) ++bufSize; | ||
833 | ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize); | ||
834 | } | ||
835 | |||
836 | if (ret != 0) | ||
837 | { | ||
838 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
839 | } | ||
840 | return buf.ToString(); | ||
841 | } | ||
842 | |||
843 | /// <summary> | ||
844 | /// [MSI 3.1] Migrates a user's application configuration data to a new SID. | ||
845 | /// </summary> | ||
846 | /// <param name="oldSid">Previous user SID that data is to be migrated from</param> | ||
847 | /// <param name="newSid">New user SID that data is to be migrated to</param> | ||
848 | /// <remarks><p> | ||
849 | /// Win32 MSI API: | ||
850 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msinotifysidchange.asp">MsiNotifySidChange</a> | ||
851 | /// </p></remarks> | ||
852 | public static void NotifySidChange(string oldSid, string newSid) | ||
853 | { | ||
854 | uint ret = NativeMethods.MsiNotifySidChange(oldSid, newSid); | ||
855 | if (ret != 0) | ||
856 | { | ||
857 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
858 | } | ||
859 | } | ||
860 | |||
861 | private static void CheckInstallResult(uint ret) | ||
862 | { | ||
863 | switch (ret) | ||
864 | { | ||
865 | case (uint) NativeMethods.Error.SUCCESS: break; | ||
866 | case (uint) NativeMethods.Error.SUCCESS_REBOOT_REQUIRED: Installer.rebootRequired = true; break; | ||
867 | case (uint) NativeMethods.Error.SUCCESS_REBOOT_INITIATED: Installer.rebootInitiated = true; break; | ||
868 | default: throw InstallerException.ExceptionFromReturnCode(ret); | ||
869 | } | ||
870 | } | ||
871 | |||
872 | private static int GetPatchStringDataType(string patchData) | ||
873 | { | ||
874 | if (patchData.IndexOf("<", StringComparison.Ordinal) >= 0 && | ||
875 | patchData.IndexOf(">", StringComparison.Ordinal) >= 0) | ||
876 | { | ||
877 | return 2; // XML blob | ||
878 | } | ||
879 | else if (String.Compare(Path.GetExtension(patchData), ".xml", | ||
880 | StringComparison.OrdinalIgnoreCase) == 0) | ||
881 | { | ||
882 | return 1; // XML file path | ||
883 | } | ||
884 | else | ||
885 | { | ||
886 | return 0; // MSP file path | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs new file mode 100644 index 00000000..9da593d9 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs | |||
@@ -0,0 +1,270 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Reflection; | ||
9 | using System.Globalization; | ||
10 | using System.Collections.Generic; | ||
11 | using System.Diagnostics.CodeAnalysis; | ||
12 | |||
13 | public static partial class Installer | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Advertises a product to the local computer. | ||
17 | /// </summary> | ||
18 | /// <param name="packagePath">Path to the package of the product being advertised</param> | ||
19 | /// <param name="perUser">True if the product is user-assigned; false if it is machine-assigned.</param> | ||
20 | /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param> | ||
21 | /// <param name="locale">The language to use if the source supports multiple languages</param> | ||
22 | /// <exception cref="FileNotFoundException">the specified package file does not exist</exception> | ||
23 | /// <seealso cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/> | ||
24 | /// <remarks><p> | ||
25 | /// Win32 MSI APIs: | ||
26 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>, | ||
27 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a> | ||
28 | /// </p></remarks> | ||
29 | public static void AdvertiseProduct(string packagePath, bool perUser, string transforms, int locale) | ||
30 | { | ||
31 | if (String.IsNullOrEmpty(packagePath)) | ||
32 | { | ||
33 | throw new ArgumentNullException("packagePath"); | ||
34 | } | ||
35 | |||
36 | if (!File.Exists(packagePath)) | ||
37 | { | ||
38 | throw new FileNotFoundException(null, packagePath); | ||
39 | } | ||
40 | |||
41 | uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, new IntPtr(perUser ? 1 : 0), transforms, (ushort) locale); | ||
42 | if (ret != 0) | ||
43 | { | ||
44 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | /// <summary> | ||
49 | /// Generates an advertise script. The method enables the installer to write to a | ||
50 | /// script the registry and shortcut information used to assign or publish a product. | ||
51 | /// </summary> | ||
52 | /// <param name="packagePath">Path to the package of the product being advertised</param> | ||
53 | /// <param name="scriptFilePath">path to script file to be created with the advertise information</param> | ||
54 | /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param> | ||
55 | /// <param name="locale">The language to use if the source supports multiple languages</param> | ||
56 | /// <exception cref="FileNotFoundException">the specified package file does not exist</exception> | ||
57 | /// <seealso cref="AdvertiseProduct"/> | ||
58 | /// <remarks><p> | ||
59 | /// Win32 MSI APIs: | ||
60 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>, | ||
61 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a> | ||
62 | /// </p></remarks> | ||
63 | public static void GenerateAdvertiseScript(string packagePath, string scriptFilePath, string transforms, int locale) | ||
64 | { | ||
65 | if (String.IsNullOrEmpty(packagePath)) | ||
66 | { | ||
67 | throw new ArgumentNullException("packagePath"); | ||
68 | } | ||
69 | |||
70 | if (!File.Exists(packagePath)) | ||
71 | { | ||
72 | throw new FileNotFoundException(null, packagePath); | ||
73 | } | ||
74 | |||
75 | uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, scriptFilePath, transforms, (ushort) locale); | ||
76 | if (ret != 0) | ||
77 | { | ||
78 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | /// <summary> | ||
83 | /// Generates an advertise script. The method enables the installer to write to a | ||
84 | /// script the registry and shortcut information used to assign or publish a product. | ||
85 | /// </summary> | ||
86 | /// <param name="packagePath">Path to the package of the product being advertised</param> | ||
87 | /// <param name="scriptFilePath">path to script file to be created with the advertise information</param> | ||
88 | /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param> | ||
89 | /// <param name="locale">The language to use if the source supports multiple languages</param> | ||
90 | /// <param name="processor">Targeted processor architecture.</param> | ||
91 | /// <param name="instance">True to install multiple instances through product code changing transform. | ||
92 | /// Advertises a new instance of the product. Requires that the <paramref name="transforms"/> parameter | ||
93 | /// includes the instance transform that changes the product code.</param> | ||
94 | /// <seealso cref="AdvertiseProduct"/> | ||
95 | /// <remarks><p> | ||
96 | /// Win32 MSI APIs: | ||
97 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>, | ||
98 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a> | ||
99 | /// </p></remarks> | ||
100 | public static void GenerateAdvertiseScript( | ||
101 | string packagePath, | ||
102 | string scriptFilePath, | ||
103 | string transforms, | ||
104 | int locale, | ||
105 | ProcessorArchitecture processor, | ||
106 | bool instance) | ||
107 | { | ||
108 | if (String.IsNullOrEmpty(packagePath)) | ||
109 | { | ||
110 | throw new ArgumentNullException("packagePath"); | ||
111 | } | ||
112 | |||
113 | if (String.IsNullOrEmpty(scriptFilePath)) | ||
114 | { | ||
115 | throw new ArgumentNullException("scriptFilePath"); | ||
116 | } | ||
117 | |||
118 | if (!File.Exists(packagePath)) | ||
119 | { | ||
120 | throw new FileNotFoundException(null, packagePath); | ||
121 | } | ||
122 | |||
123 | uint platform = 0; | ||
124 | switch (processor) | ||
125 | { | ||
126 | case ProcessorArchitecture.X86: platform = (uint) 1; break; | ||
127 | case ProcessorArchitecture.IA64: platform = (uint) 2; break; | ||
128 | case ProcessorArchitecture.Amd64: platform = (uint) 4; break; | ||
129 | } | ||
130 | |||
131 | uint ret = NativeMethods.MsiAdvertiseProductEx( | ||
132 | packagePath, | ||
133 | scriptFilePath, | ||
134 | transforms, | ||
135 | (ushort) locale, | ||
136 | platform, | ||
137 | instance ? (uint) 1 : 0); | ||
138 | if (ret != 0) | ||
139 | { | ||
140 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | /// <summary> | ||
145 | /// Copies an advertise script file to the local computer. | ||
146 | /// </summary> | ||
147 | /// <param name="scriptFile">Path to a script file generated by | ||
148 | /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param> | ||
149 | /// <param name="flags">Flags controlling advertisement</param> | ||
150 | /// <param name="removeItems">True if specified items are to be removed instead of being created</param> | ||
151 | /// <remarks><p> | ||
152 | /// The process calling this function must be running under the LocalSystem account. To advertise an | ||
153 | /// application for per-user installation to a targeted user, the thread that calls this function must | ||
154 | /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted | ||
155 | /// user, the application is advertised to all users for installation with elevated privileges. | ||
156 | /// </p></remarks> | ||
157 | [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "flags")] | ||
158 | public static void AdvertiseScript(string scriptFile, int flags, bool removeItems) | ||
159 | { | ||
160 | uint ret = NativeMethods.MsiAdvertiseScript(scriptFile, (uint) flags, IntPtr.Zero, removeItems); | ||
161 | if (ret != 0) | ||
162 | { | ||
163 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Processes an advertise script file into the specified locations. | ||
169 | /// </summary> | ||
170 | /// <param name="scriptFile">Path to a script file generated by | ||
171 | /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param> | ||
172 | /// <param name="iconFolder">An optional path to a folder in which advertised icon files and transform | ||
173 | /// files are located. If this parameter is null, no icon or transform files are written.</param> | ||
174 | /// <param name="shortcuts">True if shortcuts should be created</param> | ||
175 | /// <param name="removeItems">True if specified items are to be removed instead of created</param> | ||
176 | /// <remarks><p> | ||
177 | /// The process calling this function must be running under the LocalSystem account. To advertise an | ||
178 | /// application for per-user installation to a targeted user, the thread that calls this function must | ||
179 | /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted | ||
180 | /// user, the application is advertised to all users for installation with elevated privileges. | ||
181 | /// </p><p> | ||
182 | /// Win32 MSI API: | ||
183 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessadvertisescript.asp">MsiProcessAdvertiseScript</a> | ||
184 | /// </p></remarks> | ||
185 | public static void ProcessAdvertiseScript(string scriptFile, string iconFolder, bool shortcuts, bool removeItems) | ||
186 | { | ||
187 | uint ret = NativeMethods.MsiProcessAdvertiseScript(scriptFile, iconFolder, IntPtr.Zero, shortcuts, removeItems); | ||
188 | if (ret != 0) | ||
189 | { | ||
190 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
191 | } | ||
192 | } | ||
193 | |||
194 | /// <summary> | ||
195 | /// Gets product information for an installer script file. | ||
196 | /// </summary> | ||
197 | /// <param name="scriptFile">Path to a script file generated by | ||
198 | /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param> | ||
199 | /// <returns>ProductInstallation stub with advertise-related properties filled in.</returns> | ||
200 | /// <exception cref="ArgumentOutOfRangeException">An invalid product property was requested</exception> | ||
201 | /// <remarks><p> | ||
202 | /// Only the following properties will be filled in in the returned object:<ul> | ||
203 | /// <li><see cref="ProductInstallation.ProductCode"/></li> | ||
204 | /// <li><see cref="ProductInstallation.AdvertisedLanguage"/></li> | ||
205 | /// <li><see cref="ProductInstallation.AdvertisedVersion"/></li> | ||
206 | /// <li><see cref="ProductInstallation.AdvertisedProductName"/></li> | ||
207 | /// <li><see cref="ProductInstallation.AdvertisedPackageName"/></li> | ||
208 | /// </ul>Other properties will be null. | ||
209 | /// </p><p> | ||
210 | /// Win32 MSI API: | ||
211 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfofromscript.asp">MsiGetProductInfoFromScript</a> | ||
212 | /// </p></remarks> | ||
213 | public static ProductInstallation GetProductInfoFromScript(string scriptFile) | ||
214 | { | ||
215 | if (String.IsNullOrEmpty(scriptFile)) | ||
216 | { | ||
217 | throw new ArgumentNullException("scriptFile"); | ||
218 | } | ||
219 | StringBuilder productCodeBuf = new StringBuilder(40); | ||
220 | ushort lang; | ||
221 | uint ver; | ||
222 | StringBuilder productNameBuf = new StringBuilder(100); | ||
223 | StringBuilder packageNameBuf = new StringBuilder(40); | ||
224 | uint productCodeBufSize = (uint) productCodeBuf.Capacity; | ||
225 | uint productNameBufSize = (uint) productNameBuf.Capacity; | ||
226 | uint packageNameBufSize = (uint) packageNameBuf.Capacity; | ||
227 | uint ret = NativeMethods.MsiGetProductInfoFromScript( | ||
228 | scriptFile, | ||
229 | productCodeBuf, | ||
230 | out lang, | ||
231 | out ver, | ||
232 | productNameBuf, | ||
233 | ref productNameBufSize, | ||
234 | packageNameBuf, | ||
235 | ref packageNameBufSize); | ||
236 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
237 | { | ||
238 | productCodeBuf.Capacity = (int) ++productCodeBufSize; | ||
239 | productNameBuf.Capacity = (int) ++productNameBufSize; | ||
240 | packageNameBuf.Capacity = (int) ++packageNameBufSize; | ||
241 | ret = NativeMethods.MsiGetProductInfoFromScript( | ||
242 | scriptFile, | ||
243 | productCodeBuf, | ||
244 | out lang, | ||
245 | out ver, | ||
246 | productNameBuf, | ||
247 | ref productNameBufSize, | ||
248 | packageNameBuf, | ||
249 | ref packageNameBufSize); | ||
250 | } | ||
251 | |||
252 | if (ret != 0) | ||
253 | { | ||
254 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
255 | } | ||
256 | uint verPart1 = ver >> 24; | ||
257 | uint verPart2 = (ver & 0x00FFFFFF) >> 16; | ||
258 | uint verPart3 = ver & 0x0000FFFF; | ||
259 | Version version = new Version((int) verPart1, (int) verPart2, (int) verPart3); | ||
260 | |||
261 | IDictionary<string, string> props = new Dictionary<string, string>(); | ||
262 | props["ProductCode"] = productCodeBuf.ToString(); | ||
263 | props["Language"] = lang.ToString(CultureInfo.InvariantCulture); | ||
264 | props["Version"] = version.ToString(); | ||
265 | props["ProductName"] = productNameBuf.ToString(); | ||
266 | props["PackageName"] = packageNameBuf.ToString(); | ||
267 | return new ProductInstallation(props); | ||
268 | } | ||
269 | } | ||
270 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs new file mode 100644 index 00000000..8d9cf0a1 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs | |||
@@ -0,0 +1,472 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Resources; | ||
9 | using System.Reflection; | ||
10 | using System.Collections.Generic; | ||
11 | using System.Globalization; | ||
12 | using System.Runtime.InteropServices; | ||
13 | using System.Diagnostics.CodeAnalysis; | ||
14 | |||
15 | public static partial class Installer | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Gets the current version of the installer. | ||
19 | /// </summary> | ||
20 | public static Version Version | ||
21 | { | ||
22 | get | ||
23 | { | ||
24 | // TODO: Use the extended form of version info to get the 4th component of the verison. | ||
25 | uint[] dllVersionInfo = new uint[5]; | ||
26 | dllVersionInfo[0] = 20; | ||
27 | int hr = NativeMethods.DllGetVersion(dllVersionInfo); | ||
28 | if (hr != 0) | ||
29 | { | ||
30 | Marshal.ThrowExceptionForHR(hr); | ||
31 | } | ||
32 | |||
33 | return new Version((int) dllVersionInfo[1], (int) dllVersionInfo[2], (int) dllVersionInfo[3]); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | internal static ResourceManager ErrorResources | ||
38 | { | ||
39 | get | ||
40 | { | ||
41 | if (errorResources == null) | ||
42 | { | ||
43 | errorResources = new ResourceManager(typeof(Installer).Namespace + ".Errors", typeof(Installer).Assembly); | ||
44 | } | ||
45 | return errorResources; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Gets a Windows Installer error message in the system default language. | ||
51 | /// </summary> | ||
52 | /// <param name="errorNumber">The error number.</param> | ||
53 | /// <returns>The message string, or null if the error message is not found.</returns> | ||
54 | /// <remarks><p> | ||
55 | /// The returned string may have tokens such as [2] and [3] that are meant to be substituted | ||
56 | /// with context-specific values. | ||
57 | /// </p><p> | ||
58 | /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always | ||
59 | /// returned in English. | ||
60 | /// </p></remarks> | ||
61 | public static string GetErrorMessage(int errorNumber) | ||
62 | { | ||
63 | return Installer.GetErrorMessage(errorNumber, null); | ||
64 | } | ||
65 | |||
66 | /// <summary> | ||
67 | /// Gets a Windows Installer error message in a specified language. | ||
68 | /// </summary> | ||
69 | /// <param name="errorNumber">The error number.</param> | ||
70 | /// <param name="culture">The locale for the message.</param> | ||
71 | /// <returns>The message string, or null if the error message or locale is not found.</returns> | ||
72 | /// <remarks><p> | ||
73 | /// The returned string may have tokens such as [2] and [3] that are meant to be substituted | ||
74 | /// with context-specific values. | ||
75 | /// </p><p> | ||
76 | /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always | ||
77 | /// returned in English. | ||
78 | /// </p></remarks> | ||
79 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
80 | public static string GetErrorMessage(int errorNumber, CultureInfo culture) | ||
81 | { | ||
82 | if (culture == null) | ||
83 | { | ||
84 | culture = CultureInfo.CurrentCulture; | ||
85 | } | ||
86 | |||
87 | string msg = Installer.ErrorResources.GetString( | ||
88 | errorNumber.ToString(CultureInfo.InvariantCulture.NumberFormat), | ||
89 | culture); | ||
90 | if (msg == null) | ||
91 | { | ||
92 | string msiMsgModule = Path.Combine( | ||
93 | Environment.SystemDirectory, "msimsg.dll"); | ||
94 | msg = Installer.GetMessageFromModule( | ||
95 | msiMsgModule, errorNumber, culture); | ||
96 | } | ||
97 | return msg; | ||
98 | } | ||
99 | |||
100 | private static string GetMessageFromModule( | ||
101 | string modulePath, int errorNumber, CultureInfo culture) | ||
102 | { | ||
103 | const uint LOAD_LIBRARY_AS_DATAFILE = 2; | ||
104 | const int RT_RCDATA = 10; | ||
105 | |||
106 | IntPtr msgModule = NativeMethods.LoadLibraryEx( | ||
107 | modulePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); | ||
108 | if (msgModule == IntPtr.Zero) | ||
109 | { | ||
110 | return null; | ||
111 | } | ||
112 | |||
113 | try | ||
114 | { | ||
115 | // On pre-Vista systems, the messages are stored as RCDATA resources. | ||
116 | |||
117 | int lcid = (culture == CultureInfo.InvariantCulture) ? | ||
118 | 0 : culture.LCID; | ||
119 | IntPtr resourceInfo = NativeMethods.FindResourceEx( | ||
120 | msgModule, | ||
121 | new IntPtr(RT_RCDATA), | ||
122 | new IntPtr(errorNumber), | ||
123 | (ushort) lcid); | ||
124 | if (resourceInfo != IntPtr.Zero) | ||
125 | { | ||
126 | IntPtr resourceData = NativeMethods.LoadResource( | ||
127 | msgModule, resourceInfo); | ||
128 | IntPtr resourcePtr = NativeMethods.LockResource(resourceData); | ||
129 | |||
130 | if (lcid == 0) | ||
131 | { | ||
132 | string msg = Marshal.PtrToStringAnsi(resourcePtr); | ||
133 | return msg; | ||
134 | } | ||
135 | else | ||
136 | { | ||
137 | int len = 0; | ||
138 | while (Marshal.ReadByte(resourcePtr, len) != 0) | ||
139 | { | ||
140 | len++; | ||
141 | } | ||
142 | byte[] msgBytes = new byte[len + 1]; | ||
143 | Marshal.Copy(resourcePtr, msgBytes, 0, msgBytes.Length); | ||
144 | Encoding encoding = Encoding.GetEncoding( | ||
145 | culture.TextInfo.ANSICodePage); | ||
146 | string msg = encoding.GetString(msgBytes); | ||
147 | return msg; | ||
148 | } | ||
149 | } | ||
150 | else | ||
151 | { | ||
152 | // On Vista (and above?), the messages are stored in the module message table. | ||
153 | // They're actually in MUI files, and the redirection happens automatically here. | ||
154 | |||
155 | const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; | ||
156 | const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800; | ||
157 | const uint MESSAGE_OFFSET = 20000; // Not documented, but observed on Vista | ||
158 | |||
159 | StringBuilder buf = new StringBuilder(1024); | ||
160 | uint formatCount = NativeMethods.FormatMessage( | ||
161 | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
162 | msgModule, | ||
163 | (uint) errorNumber + MESSAGE_OFFSET, | ||
164 | (ushort) lcid, | ||
165 | buf, | ||
166 | (uint) buf.Capacity, | ||
167 | IntPtr.Zero); | ||
168 | |||
169 | return formatCount != 0 ? buf.ToString().Trim() : null; | ||
170 | } | ||
171 | } | ||
172 | finally | ||
173 | { | ||
174 | NativeMethods.FreeLibrary(msgModule); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /// <summary> | ||
179 | /// Gets a formatted Windows Installer error message in the system default language. | ||
180 | /// </summary> | ||
181 | /// <param name="errorRecord">Error record containing the error number in the first field, and | ||
182 | /// error-specific parameters in the other fields.</param> | ||
183 | /// <returns>The message string, or null if the error message is not found.</returns> | ||
184 | /// <remarks><p> | ||
185 | /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always | ||
186 | /// returned in English. | ||
187 | /// </p></remarks> | ||
188 | public static string GetErrorMessage(Record errorRecord) { return Installer.GetErrorMessage(errorRecord, null); } | ||
189 | |||
190 | /// <summary> | ||
191 | /// Gets a formatted Windows Installer error message in a specified language. | ||
192 | /// </summary> | ||
193 | /// <param name="errorRecord">Error record containing the error number in the first field, and | ||
194 | /// error-specific parameters in the other fields.</param> | ||
195 | /// <param name="culture">The locale for the message.</param> | ||
196 | /// <returns>The message string, or null if the error message or locale is not found.</returns> | ||
197 | /// <remarks><p> | ||
198 | /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always | ||
199 | /// returned in English. | ||
200 | /// </p></remarks> | ||
201 | public static string GetErrorMessage(Record errorRecord, CultureInfo culture) | ||
202 | { | ||
203 | if (errorRecord == null) | ||
204 | { | ||
205 | throw new ArgumentNullException("errorRecord"); | ||
206 | } | ||
207 | int errorNumber; | ||
208 | if (errorRecord.FieldCount < 1 || (errorNumber = (int) errorRecord.GetInteger(1)) == 0) | ||
209 | { | ||
210 | throw new ArgumentOutOfRangeException("errorRecord"); | ||
211 | } | ||
212 | |||
213 | string msg = Installer.GetErrorMessage(errorNumber, culture); | ||
214 | if (msg != null) | ||
215 | { | ||
216 | errorRecord.FormatString = msg; | ||
217 | msg = errorRecord.ToString((IFormatProvider)null); | ||
218 | } | ||
219 | return msg; | ||
220 | } | ||
221 | |||
222 | /// <summary> | ||
223 | /// Gets the version string of the path specified using the format that the installer | ||
224 | /// expects to find it in in the database. | ||
225 | /// </summary> | ||
226 | /// <param name="path">Path to the file</param> | ||
227 | /// <returns>Version string in the "#.#.#.#" format, or an empty string if the file | ||
228 | /// does not contain version information</returns> | ||
229 | /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception> | ||
230 | /// <remarks><p> | ||
231 | /// Win32 MSI API: | ||
232 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a> | ||
233 | /// </p></remarks> | ||
234 | public static string GetFileVersion(string path) | ||
235 | { | ||
236 | StringBuilder version = new StringBuilder(20); | ||
237 | uint verBufSize = 0, langBufSize = 0; | ||
238 | uint ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize); | ||
239 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
240 | { | ||
241 | version.Capacity = (int) ++verBufSize; | ||
242 | ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize); | ||
243 | } | ||
244 | |||
245 | if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID) | ||
246 | { | ||
247 | if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || | ||
248 | ret == (uint) NativeMethods.Error.ACCESS_DENIED) | ||
249 | { | ||
250 | throw new FileNotFoundException(null, path); | ||
251 | } | ||
252 | else | ||
253 | { | ||
254 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
255 | } | ||
256 | } | ||
257 | return version.ToString(); | ||
258 | } | ||
259 | |||
260 | /// <summary> | ||
261 | /// Gets the language string of the path specified using the format that the installer | ||
262 | /// expects to find them in in the database. | ||
263 | /// </summary> | ||
264 | /// <param name="path">Path to the file</param> | ||
265 | /// <returns>Language string in the form of a decimal language ID, or an empty string if the file | ||
266 | /// does not contain a language ID</returns> | ||
267 | /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception> | ||
268 | /// <remarks><p> | ||
269 | /// Win32 MSI API: | ||
270 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a> | ||
271 | /// </p></remarks> | ||
272 | public static string GetFileLanguage(string path) | ||
273 | { | ||
274 | StringBuilder language = new StringBuilder("", 10); | ||
275 | uint verBufSize = 0, langBufSize = 0; | ||
276 | uint ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize); | ||
277 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
278 | { | ||
279 | language.Capacity = (int) ++langBufSize; | ||
280 | ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize); | ||
281 | } | ||
282 | |||
283 | if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID) | ||
284 | { | ||
285 | if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || | ||
286 | ret == (uint) NativeMethods.Error.ACCESS_DENIED) | ||
287 | { | ||
288 | throw new FileNotFoundException(null, path); | ||
289 | } | ||
290 | else | ||
291 | { | ||
292 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
293 | } | ||
294 | } | ||
295 | return language.ToString(); | ||
296 | } | ||
297 | |||
298 | /// <summary> | ||
299 | /// Gets a 128-bit hash of the specified file. | ||
300 | /// </summary> | ||
301 | /// <param name="path">Path to the file</param> | ||
302 | /// <param name="hash">Integer array of length 4 which receives the | ||
303 | /// four 32-bit parts of the hash value.</param> | ||
304 | /// <exception cref="FileNotFoundException">the file does not exist or | ||
305 | /// could not be read</exception> | ||
306 | /// <remarks><p> | ||
307 | /// Win32 MSI API: | ||
308 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfilehash.asp">MsiGetFileHash</a> | ||
309 | /// </p></remarks> | ||
310 | public static void GetFileHash(string path, int[] hash) | ||
311 | { | ||
312 | if (hash == null) | ||
313 | { | ||
314 | throw new ArgumentNullException("hash"); | ||
315 | } | ||
316 | |||
317 | uint[] tempHash = new uint[5]; | ||
318 | tempHash[0] = 20; | ||
319 | uint ret = NativeMethods.MsiGetFileHash(path, 0, tempHash); | ||
320 | if (ret != 0) | ||
321 | { | ||
322 | if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || | ||
323 | ret == (uint) NativeMethods.Error.ACCESS_DENIED) | ||
324 | { | ||
325 | throw new FileNotFoundException(null, path); | ||
326 | } | ||
327 | else | ||
328 | { | ||
329 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | for (int i = 0; i < 4; i++) | ||
334 | { | ||
335 | hash[i] = unchecked ((int) tempHash[i + 1]); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Examines a shortcut and returns its product, feature name, and component if available. | ||
341 | /// </summary> | ||
342 | /// <param name="shortcut">Full path to a shortcut</param> | ||
343 | /// <returns>ShortcutTarget structure containing target product code, feature, and component code</returns> | ||
344 | /// <remarks><p> | ||
345 | /// Win32 MSI API: | ||
346 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetshortcuttarget.asp">MsiGetShortcutTarget</a> | ||
347 | /// </p></remarks> | ||
348 | public static ShortcutTarget GetShortcutTarget(string shortcut) | ||
349 | { | ||
350 | StringBuilder productBuf = new StringBuilder(40); | ||
351 | StringBuilder featureBuf = new StringBuilder(40); | ||
352 | StringBuilder componentBuf = new StringBuilder(40); | ||
353 | |||
354 | uint ret = NativeMethods.MsiGetShortcutTarget(shortcut, productBuf, featureBuf, componentBuf); | ||
355 | if (ret != 0) | ||
356 | { | ||
357 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
358 | } | ||
359 | return new ShortcutTarget( | ||
360 | productBuf.Length > 0 ? productBuf.ToString() : null, | ||
361 | featureBuf.Length > 0 ? featureBuf.ToString() : null, | ||
362 | componentBuf.Length > 0 ? componentBuf.ToString() : null); | ||
363 | } | ||
364 | |||
365 | /// <summary> | ||
366 | /// Verifies that the given file is an installation package. | ||
367 | /// </summary> | ||
368 | /// <param name="packagePath">Path to the package</param> | ||
369 | /// <returns>True if the file is an installation package; false otherwise.</returns> | ||
370 | /// <exception cref="FileNotFoundException">the specified package file does not exist</exception> | ||
371 | /// <exception cref="InstallerException">the package file could not be opened</exception> | ||
372 | /// <remarks><p> | ||
373 | /// Win32 MSI API: | ||
374 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifypackage.asp">MsiVerifyPackage</a> | ||
375 | /// </p></remarks> | ||
376 | public static bool VerifyPackage(string packagePath) | ||
377 | { | ||
378 | if (String.IsNullOrEmpty(packagePath)) | ||
379 | { | ||
380 | throw new ArgumentNullException("packagePath"); | ||
381 | } | ||
382 | |||
383 | if (!File.Exists(packagePath)) | ||
384 | { | ||
385 | throw new FileNotFoundException(null, packagePath); | ||
386 | } | ||
387 | |||
388 | uint ret = NativeMethods.MsiVerifyPackage(packagePath); | ||
389 | if (ret == (uint) NativeMethods.Error.INSTALL_PACKAGE_INVALID) | ||
390 | { | ||
391 | return false; | ||
392 | } | ||
393 | else if (ret != 0) | ||
394 | { | ||
395 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
396 | } | ||
397 | return true; | ||
398 | } | ||
399 | |||
400 | /// <summary> | ||
401 | /// [MSI 4.0] Gets the list of files that can be updated by one or more patches. | ||
402 | /// </summary> | ||
403 | /// <param name="productCode">ProductCode (GUID) of the product which is | ||
404 | /// the target of the patches</param> | ||
405 | /// <param name="patches">list of file paths of one or more patches to be | ||
406 | /// analyzed</param> | ||
407 | /// <returns>List of absolute paths of files that can be updated when the | ||
408 | /// patches are applied on this system.</returns> | ||
409 | /// <remarks><p> | ||
410 | /// Win32 MSI API: | ||
411 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchfilelist.asp">MsiGetPatchFileList</a> | ||
412 | /// </p></remarks> | ||
413 | public static IList<string> GetPatchFileList(string productCode, IList<string> patches) | ||
414 | { | ||
415 | if (String.IsNullOrEmpty(productCode)) | ||
416 | { | ||
417 | throw new ArgumentNullException("productCode"); | ||
418 | } | ||
419 | |||
420 | if (patches == null || patches.Count == 0) | ||
421 | { | ||
422 | throw new ArgumentNullException("patches"); | ||
423 | } | ||
424 | |||
425 | StringBuilder patchList = new StringBuilder(); | ||
426 | foreach (string patch in patches) | ||
427 | { | ||
428 | if (patch != null) | ||
429 | { | ||
430 | if (patchList.Length != 0) | ||
431 | { | ||
432 | patchList.Append(';'); | ||
433 | } | ||
434 | |||
435 | patchList.Append(patch); | ||
436 | } | ||
437 | } | ||
438 | |||
439 | if (patchList.Length == 0) | ||
440 | { | ||
441 | throw new ArgumentNullException("patches"); | ||
442 | } | ||
443 | |||
444 | IntPtr phFileRecords; | ||
445 | uint cFiles; | ||
446 | |||
447 | uint ret = NativeMethods.MsiGetPatchFileList( | ||
448 | productCode, | ||
449 | patchList.ToString(), | ||
450 | out cFiles, | ||
451 | out phFileRecords); | ||
452 | if (ret != 0) | ||
453 | { | ||
454 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
455 | } | ||
456 | |||
457 | List<string> files = new List<string>(); | ||
458 | |||
459 | for (uint i = 0; i < cFiles; i++) | ||
460 | { | ||
461 | int hFileRec = Marshal.ReadInt32(phFileRecords, (int) i); | ||
462 | |||
463 | using (Record fileRec = new Record(hFileRec, true, null)) | ||
464 | { | ||
465 | files.Add(fileRec.GetString(1)); | ||
466 | } | ||
467 | } | ||
468 | |||
469 | return files; | ||
470 | } | ||
471 | } | ||
472 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs new file mode 100644 index 00000000..7b196b3e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics.CodeAnalysis; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Represents a media disk source of a product or a patch. | ||
10 | /// </summary> | ||
11 | [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] | ||
12 | public struct MediaDisk | ||
13 | { | ||
14 | private int diskId; | ||
15 | private string volumeLabel; | ||
16 | private string diskPrompt; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Creates a new media disk. | ||
20 | /// </summary> | ||
21 | /// <param name="diskId"></param> | ||
22 | /// <param name="volumeLabel"></param> | ||
23 | /// <param name="diskPrompt"></param> | ||
24 | public MediaDisk(int diskId, string volumeLabel, string diskPrompt) | ||
25 | { | ||
26 | this.diskId = diskId; | ||
27 | this.volumeLabel = volumeLabel; | ||
28 | this.diskPrompt = diskPrompt; | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Gets or sets the disk id of the media disk. | ||
33 | /// </summary> | ||
34 | public int DiskId | ||
35 | { | ||
36 | get { return this.diskId; } | ||
37 | set { this.diskId = value; } | ||
38 | } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Gets or sets the volume label of the media disk. | ||
42 | /// </summary> | ||
43 | public string VolumeLabel | ||
44 | { | ||
45 | get { return this.volumeLabel; } | ||
46 | set { this.volumeLabel = value; } | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Gets or sets the disk prompt of the media disk. | ||
51 | /// </summary> | ||
52 | public string DiskPrompt | ||
53 | { | ||
54 | get { return this.diskPrompt; } | ||
55 | set { this.diskPrompt = value; } | ||
56 | } | ||
57 | } | ||
58 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs new file mode 100644 index 00000000..a438b640 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs | |||
@@ -0,0 +1,309 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Runtime.InteropServices; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | using IStream = System.Runtime.InteropServices.ComTypes.IStream; | ||
11 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; | ||
12 | using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; | ||
13 | |||
14 | [Guid("0000000b-0000-0000-C000-000000000046")] | ||
15 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
16 | internal interface IStorage | ||
17 | { | ||
18 | [return: MarshalAs(UnmanagedType.Interface)] | ||
19 | IStream CreateStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2); | ||
20 | [return: MarshalAs(UnmanagedType.Interface)] | ||
21 | IStream OpenStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr reserved1, uint grfMode, uint reserved2); | ||
22 | [return: MarshalAs(UnmanagedType.Interface)] | ||
23 | IStorage CreateStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2); | ||
24 | [return: MarshalAs(UnmanagedType.Interface)] | ||
25 | IStorage OpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved); | ||
26 | void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest); | ||
27 | void MoveElementTo([MarshalAs(UnmanagedType.LPWStr)] string wcsName, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName, uint grfFlags); | ||
28 | void Commit(uint grfCommitFlags); | ||
29 | void Revert(); | ||
30 | IntPtr EnumElements(uint reserved1, IntPtr reserved2, uint reserved3); | ||
31 | void DestroyElement([MarshalAs(UnmanagedType.LPWStr)] string wcsName); | ||
32 | void RenameElement([MarshalAs(UnmanagedType.LPWStr)] string wcsOldName, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName); | ||
33 | void SetElementTimes([MarshalAs(UnmanagedType.LPWStr)] string wcsName, ref FILETIME ctime, ref FILETIME atime, ref FILETIME mtime); | ||
34 | void SetClass(ref Guid clsid); | ||
35 | void SetStateBits(uint grfStateBits, uint grfMask); | ||
36 | void Stat(ref STATSTG statstg, uint grfStatFlag); | ||
37 | } | ||
38 | |||
39 | internal static class NativeMethods | ||
40 | { | ||
41 | internal enum Error : uint | ||
42 | { | ||
43 | SUCCESS = 0, | ||
44 | FILE_NOT_FOUND = 2, | ||
45 | PATH_NOT_FOUND = 3, | ||
46 | ACCESS_DENIED = 5, | ||
47 | INVALID_HANDLE = 6, | ||
48 | INVALID_DATA = 13, | ||
49 | INVALID_PARAMETER = 87, | ||
50 | OPEN_FAILED = 110, | ||
51 | DISK_FULL = 112, | ||
52 | CALL_NOT_IMPLEMENTED = 120, | ||
53 | BAD_PATHNAME = 161, | ||
54 | NO_DATA = 232, | ||
55 | MORE_DATA = 234, | ||
56 | NO_MORE_ITEMS = 259, | ||
57 | DIRECTORY = 267, | ||
58 | INSTALL_USEREXIT = 1602, | ||
59 | INSTALL_FAILURE = 1603, | ||
60 | FILE_INVALID = 1006, | ||
61 | UNKNOWN_PRODUCT = 1605, | ||
62 | UNKNOWN_FEATURE = 1606, | ||
63 | UNKNOWN_COMPONENT = 1607, | ||
64 | UNKNOWN_PROPERTY = 1608, | ||
65 | INVALID_HANDLE_STATE = 1609, | ||
66 | INSTALL_SOURCE_ABSENT = 1612, | ||
67 | BAD_QUERY_SYNTAX = 1615, | ||
68 | INSTALL_PACKAGE_INVALID = 1620, | ||
69 | FUNCTION_FAILED = 1627, | ||
70 | INVALID_TABLE = 1628, | ||
71 | DATATYPE_MISMATCH = 1629, | ||
72 | CREATE_FAILED = 1631, | ||
73 | SUCCESS_REBOOT_INITIATED = 1641, | ||
74 | SUCCESS_REBOOT_REQUIRED = 3010, | ||
75 | } | ||
76 | |||
77 | internal enum SourceType : int | ||
78 | { | ||
79 | Unknown = 0, | ||
80 | Network = 1, | ||
81 | Url = 2, | ||
82 | Media = 3, | ||
83 | } | ||
84 | |||
85 | [Flags] | ||
86 | internal enum STGM : uint | ||
87 | { | ||
88 | DIRECT = 0x00000000, | ||
89 | TRANSACTED = 0x00010000, | ||
90 | SIMPLE = 0x08000000, | ||
91 | |||
92 | READ = 0x00000000, | ||
93 | WRITE = 0x00000001, | ||
94 | READWRITE = 0x00000002, | ||
95 | |||
96 | SHARE_DENY_NONE = 0x00000040, | ||
97 | SHARE_DENY_READ = 0x00000030, | ||
98 | SHARE_DENY_WRITE = 0x00000020, | ||
99 | SHARE_EXCLUSIVE = 0x00000010, | ||
100 | |||
101 | PRIORITY = 0x00040000, | ||
102 | DELETEONRELEASE = 0x04000000, | ||
103 | NOSCRATCH = 0x00100000, | ||
104 | |||
105 | CREATE = 0x00001000, | ||
106 | CONVERT = 0x00020000, | ||
107 | FAILIFTHERE = 0x00000000, | ||
108 | |||
109 | NOSNAPSHOT = 0x00200000, | ||
110 | DIRECT_SWMR = 0x00400000, | ||
111 | } | ||
112 | |||
113 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int DllGetVersion(uint[] dvi); | ||
114 | |||
115 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, ref IntPtr phWnd); | ||
116 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, IntPtr phWnd); | ||
117 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern NativeExternalUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIHandler puiHandler, uint dwMessageFilter, IntPtr pvContext); | ||
118 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnableLog(uint dwLogMode, string szLogFile, uint dwLogAttributes); | ||
119 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProducts(uint iProductIndex, StringBuilder lpProductBuf); | ||
120 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfo(string szProduct, string szProperty, StringBuilder lpValueBuf, ref uint pcchValueBuf); | ||
121 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatches(string szProduct, uint iPatchIndex, StringBuilder lpPatchBuf, StringBuilder lpTransformsBuf, ref uint pcchTransformsBuf); | ||
122 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfo(string szPatch, string szAttribute, StringBuilder lpValueBuf, ref uint pcchValueBuf); | ||
123 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumFeatures(string szProduct, uint iFeatureIndex, StringBuilder lpFeatureBuf, StringBuilder lpParentBuf); | ||
124 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryFeatureState(string szProduct, string szFeature); | ||
125 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiUseFeatureEx(string szProduct, string szFeature, uint dwInstallMode, uint dwReserved); | ||
126 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryProductState(string szProduct); | ||
127 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetShortcutTarget(string szShortcut, StringBuilder szProductCode, StringBuilder szFeatureId, StringBuilder szComponentCode); | ||
128 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideComponent(string szProduct, string szFeature, string szComponent, uint dwInstallMode, StringBuilder lpPathBuf, ref uint cchPathBuf); | ||
129 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideQualifiedComponentEx(string szComponent, string szQualifier, uint dwInstallMode, string szProduct, uint dwUnused1, uint dwUnused2, StringBuilder lpPathBuf, ref uint cchPathBuf); | ||
130 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallFeature(string szFeature, string szProduct, uint dwReinstallMode); | ||
131 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallProduct(string szProduct, uint dwReinstallMode); | ||
132 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSource(string szProduct, string szUserName, uint dwReserved, string szSource); | ||
133 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAll(string szProduct, string szUserName, uint dwReserved); | ||
134 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolution(string szProduct, string szUserName, uint dwReserved); | ||
135 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCollectUserInfo(string szProduct); | ||
136 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetUserInfo(string szProduct, StringBuilder lpUserNameBuf, ref uint cchUserNameBuf, StringBuilder lpOrgNameBuf, ref uint cchOrgNameBuf, StringBuilder lpSerialBuf, ref uint cchSerialBuf); | ||
137 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenPackageEx(string szPackagePath, uint dwOptions, out int hProduct); | ||
138 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenProduct(string szProduct, out int hProduct); | ||
139 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallProduct(string szPackagePath, string szCommandLine); | ||
140 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureProductEx(string szProduct, int iInstallLevel, int eInstallState, string szCommandLine); | ||
141 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureFeature(string szProduct, string szFeature, int eInstallState); | ||
142 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); | ||
143 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, IntPtr uiOpenMode, out int hDatabase); | ||
144 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, string szPersist, out int hDatabase); | ||
145 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetDatabaseState(int hDatabase); | ||
146 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView); | ||
147 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseMerge(int hDatabase, int hDatabaseMerge, string szTableName); | ||
148 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseCommit(int hDatabase); | ||
149 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord); | ||
150 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName); | ||
151 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseExport(int hDatabase, string szTableName, string szFolderPath, string szFileName); | ||
152 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseImport(int hDatabase, string szFolderPath, string szFileName); | ||
153 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGenerateTransform(int hDatabase, int hDatabaseReference, string szTransformFile, int iReserved1, int iReserved2); | ||
154 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCreateTransformSummaryInfo(int hDatabase, int hDatabaseReference, string szTransformFile, int iErrorConditions, int iValidation); | ||
155 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseApplyTransform(int hDatabase, string szTransformFile, int iErrorConditions); | ||
156 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewExecute(int hView, int hRecord); | ||
157 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewFetch(int hView, out int hRecord); | ||
158 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewModify(int hView, int iModifyMode, int hRecord); | ||
159 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf); | ||
160 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord); | ||
161 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiCreateRecord(uint cParams); | ||
162 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf); | ||
163 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordClearData(int hRecord); | ||
164 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetFieldCount(int hRecord); | ||
165 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiRecordIsNull(int hRecord, uint iField); | ||
166 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiRecordGetInteger(int hRecord, uint iField); | ||
167 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf); | ||
168 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetInteger(int hRecord, uint iField, int iValue); | ||
169 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetString(int hRecord, uint iField, string szValue); | ||
170 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordDataSize(int hRecord, uint iField); | ||
171 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf); | ||
172 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath); | ||
173 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo); | ||
174 | //[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetPropertyCount(int hSummaryInfo, out uint uiPropertyCount); | ||
175 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf); | ||
176 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoSetProperty(int hSummaryInfo, uint uiProperty, uint uiDataType, int iValue, ref long ftValue, string szValue); | ||
177 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoPersist(int hSummaryInfo); | ||
178 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCloseHandle(int hAny); | ||
179 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileVersion(string szFilePath, StringBuilder szVersionBuf, ref uint cchVersionBuf, StringBuilder szLangBuf, ref uint cchLangBuf); | ||
180 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileHash(string szFilePath, uint dwOptions, uint[] hash); | ||
181 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetActiveDatabase(int hInstall); | ||
182 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf); | ||
183 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetProperty(int hInstall, string szName, string szValue); | ||
184 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord); | ||
185 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEvaluateCondition(int hInstall, string szCondition); | ||
186 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiGetMode(int hInstall, uint iRunMode); | ||
187 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetMode(int hInstall, uint iRunMode, [MarshalAs(UnmanagedType.Bool)] bool fState); | ||
188 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDoAction(int hInstall, string szAction); | ||
189 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSequence(int hInstall, string szTable, int iSequenceMode); | ||
190 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf); | ||
191 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf); | ||
192 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath); | ||
193 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction); | ||
194 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetComponentState(int hInstall, string szComponent, int iState); | ||
195 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction); | ||
196 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureState(int hInstall, string szFeature, int iState); | ||
197 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstallState); | ||
198 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInstallLevel(int hInstall, int iInstallLevel); | ||
199 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern ushort MsiGetLanguage(int hInstall); | ||
200 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponents(uint iComponentIndex, StringBuilder lpComponentBuf); | ||
201 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentsEx(string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid); | ||
202 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClients(string szComponent, uint iProductIndex, StringBuilder lpProductBuf); | ||
203 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClientsEx(string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint iProductIndex, StringBuilder lpProductBuf, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid); | ||
204 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPath(string szProduct, string szComponent, StringBuilder lpPathBuf, ref uint pcchBuf); | ||
205 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPathEx(string szProduct, string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, StringBuilder lpPathBuf, ref uint pcchBuf); | ||
206 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentQualifiers(string szComponent, uint iIndex, StringBuilder lpQualifierBuf, ref uint pcchQualifierBuf, StringBuilder lpApplicationDataBuf, ref uint pcchApplicationDataBuf); | ||
207 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetLastErrorRecord(); | ||
208 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumRelatedProducts(string upgradeCode, uint dwReserved, uint iProductIndex, StringBuilder lpProductBuf); | ||
209 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductCode(string szComponent, StringBuilder lpProductBuf); | ||
210 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureUsage(string szProduct, string szFeature, out uint dwUseCount, out ushort dwDateUsed); | ||
211 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost); | ||
212 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyPackage(string szPackagePath); | ||
213 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiIsProductElevated(string szProductCode, [MarshalAs(UnmanagedType.Bool)] out bool fElevated); | ||
214 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, IntPtr szScriptFilePath, string szTransforms, ushort lgidLanguage); | ||
215 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage); | ||
216 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProductEx(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage, uint dwPlatform, uint dwReserved); | ||
217 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseScript(string szScriptFile, uint dwFlags, IntPtr phRegData, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems); | ||
218 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProcessAdvertiseScript(string szScriptFile, string szIconFolder, IntPtr hRegData, [MarshalAs(UnmanagedType.Bool)] bool fShortcuts, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems); | ||
219 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoFromScript(string szScriptFile, StringBuilder lpProductBuf39, out ushort plgidLanguage, out uint pdwVersion, StringBuilder lpNameBuf, ref uint cchNameBuf, StringBuilder lpPackageBuf, ref uint cchPackageBuf); | ||
220 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideAssembly(string szAssemblyName, string szAppContext, uint dwInstallMode, uint dwAssemblyInfo, StringBuilder lpPathBuf, ref uint cchPathBuf); | ||
221 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingComponent(string szProduct, string szComponent, int eInstallState); | ||
222 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingFile(string szProduct, string szFile); | ||
223 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiLocateComponent(string szComponent, StringBuilder lpPathBuf, ref uint cchBuf); | ||
224 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductProperty(int hProduct, string szProperty, StringBuilder lpValueBuf, ref uint cchValueBuf); | ||
225 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureInfo(int hProduct, string szFeature, out uint lpAttributes, StringBuilder lpTitleBuf, ref uint cchTitleBuf, StringBuilder lpHelpBuf, ref uint cchHelpBuf); | ||
226 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyDiskSpace(int hInstall); | ||
227 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost); | ||
228 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes); | ||
229 | |||
230 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRemovePatches(string szPatchList, string szProductCode, int eUninstallType, string szPropertyList); | ||
231 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDetermineApplicablePatches(string szProductPackagePath, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1), In, Out] MsiPatchSequenceData[] pPatchInfo); | ||
232 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDeterminePatchSequence(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3), In, Out] MsiPatchSequenceData[] pPatchInfo); | ||
233 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyMultiplePatches(string szPatchPackages, string szProductCode, string szPropertiesList); | ||
234 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatchesEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwFilter, uint dwIndex, StringBuilder szPatchCode, StringBuilder szTargetProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwTargetProductContext, StringBuilder szTargetUserSid, ref uint pcchTargetUserSid); | ||
235 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfoEx(string szPatchCode, string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue); | ||
236 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProductsEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid); | ||
237 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue); | ||
238 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryFeatureStateEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szFeature, out int pdwState); | ||
239 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryComponentState(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szComponent, out int pdwState); | ||
240 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiExtractPatchXMLData(string szPatchPath, uint dwReserved, StringBuilder szXMLData, ref uint pcchXMLData); | ||
241 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumSources(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, StringBuilder szSource, ref uint pcchSource); | ||
242 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSourceEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource, uint dwIndex); | ||
243 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearSource(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource); | ||
244 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAllEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions); | ||
245 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolutionEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions); | ||
246 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListGetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, StringBuilder szValue, ref uint pcchValue); | ||
247 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListSetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, string szValue); | ||
248 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumMediaDisks(string szProductCodeOrPatchCode, string szUserSID, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, out uint pdwDiskId, StringBuilder szVolumeLabel, ref uint pcchVolumeLabel, StringBuilder szDiskPrompt, ref uint pcchDiskPrompt); | ||
249 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskId, string szVolumeLabel, string szDiskPrompt); | ||
250 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskID); | ||
251 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiNotifySidChange(string szOldSid, string szNewSid); | ||
252 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetExternalUIRecord([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIRecordHandler puiHandler, uint dwMessageFilter, IntPtr pvContext, out NativeExternalUIRecordHandler ppuiPrevHandler); | ||
253 | |||
254 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchFileList(string szProductCode, string szPatchList, out uint cFiles, out IntPtr phFileRecords); | ||
255 | |||
256 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiBeginTransaction(string szTransactionName, int dwTransactionAttributes, out int hTransaction, out IntPtr phChangeOfOwnerEvent); | ||
257 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEndTransaction(int dwTransactionState); | ||
258 | [DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiJoinTransaction(int hTransaction, int dwTransactionAttributes, out IntPtr phChangeOfOwnerEvent); | ||
259 | |||
260 | [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="LoadLibraryExW")] internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags); | ||
261 | [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FreeLibrary(IntPtr hModule); | ||
262 | [DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr FindResourceEx(IntPtr hModule, IntPtr type, IntPtr name, ushort langId); | ||
263 | [DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LoadResource(IntPtr hModule, IntPtr lpResourceInfo); | ||
264 | [DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LockResource(IntPtr resourceData); | ||
265 | [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="FormatMessageW")] internal static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments); | ||
266 | [DllImport("kernel32.dll", SetLastError=true)] internal static extern int WaitForSingleObject(IntPtr handle, int milliseconds); | ||
267 | |||
268 | [DllImport("ole32.dll")] internal static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen); | ||
269 | [DllImport("ole32.dll")] internal static extern int StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen); | ||
270 | |||
271 | [DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="MessageBoxW")] internal static extern MessageResult MessageBox(IntPtr hWnd, string lpText, string lpCaption, [MarshalAs(UnmanagedType.U4)] int uType); | ||
272 | |||
273 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] | ||
274 | internal struct MsiPatchSequenceData | ||
275 | { | ||
276 | public string szPatchData; | ||
277 | public int ePatchDataType; | ||
278 | public int dwOrder; | ||
279 | public uint dwStatus; | ||
280 | } | ||
281 | |||
282 | internal class MsiHandle : SafeHandle | ||
283 | { | ||
284 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
285 | public MsiHandle(IntPtr handle, bool ownsHandle) | ||
286 | : base(handle, ownsHandle) | ||
287 | { | ||
288 | } | ||
289 | |||
290 | public override bool IsInvalid | ||
291 | { | ||
292 | get | ||
293 | { | ||
294 | return this.handle == IntPtr.Zero; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | public static implicit operator IntPtr(MsiHandle msiHandle) | ||
299 | { | ||
300 | return msiHandle.handle; | ||
301 | } | ||
302 | |||
303 | protected override bool ReleaseHandle() | ||
304 | { | ||
305 | return RemotableNativeMethods.MsiCloseHandle((int) this.handle) == 0; | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs new file mode 100644 index 00000000..defbf64a --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs | |||
@@ -0,0 +1,413 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// The Patch object represents a unique instance of a patch that has been | ||
13 | /// registered or applied. | ||
14 | /// </summary> | ||
15 | public class PatchInstallation : Installation | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Enumerates all patch installations on the system. | ||
19 | /// </summary> | ||
20 | /// <returns>Enumeration of patch objects.</returns> | ||
21 | /// <remarks><p> | ||
22 | /// Win32 MSI API: | ||
23 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatches.asp">MsiEnumPatches</a> | ||
24 | /// </p></remarks> | ||
25 | public static IEnumerable<PatchInstallation> AllPatches | ||
26 | { | ||
27 | get | ||
28 | { | ||
29 | return PatchInstallation.GetPatches(null, null, null, UserContexts.All, PatchStates.All); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Enumerates patch installations based on certain criteria. | ||
35 | /// </summary> | ||
36 | /// <param name="patchCode">PatchCode (GUID) of the patch to be enumerated. Only | ||
37 | /// instances of patches within the scope of the context specified by the | ||
38 | /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be | ||
39 | /// enumerated. This parameter may be set to null to enumerate all patches in the specified | ||
40 | /// context.</param> | ||
41 | /// <param name="targetProductCode">ProductCode (GUID) product whose patches are to be | ||
42 | /// enumerated. If non-null, patch enumeration is restricted to instances of this product | ||
43 | /// within the specified context. If null, the patches for all products under the specified | ||
44 | /// context are enumerated.</param> | ||
45 | /// <param name="userSid">Specifies a security identifier (SID) that restricts the context | ||
46 | /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts | ||
47 | /// enumeration to the current user or any user in the system. The special SID string | ||
48 | /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter | ||
49 | /// can be set to null to restrict the enumeration scope to the current user. When | ||
50 | /// <paramref name="userSid"/> must be null.</param> | ||
51 | /// <param name="context">Specifies the user context.</param> | ||
52 | /// <param name="states">The <see cref="PatchStates"/> of patches to return.</param> | ||
53 | /// <remarks><p> | ||
54 | /// Win32 MSI APIs: | ||
55 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatchesex.asp">MsiEnumPatchesEx</a> | ||
56 | /// </p></remarks> | ||
57 | public static IEnumerable<PatchInstallation> GetPatches( | ||
58 | string patchCode, | ||
59 | string targetProductCode, | ||
60 | string userSid, | ||
61 | UserContexts context, | ||
62 | PatchStates states) | ||
63 | { | ||
64 | StringBuilder buf = new StringBuilder(40); | ||
65 | StringBuilder targetProductBuf = new StringBuilder(40); | ||
66 | UserContexts targetContext; | ||
67 | StringBuilder targetSidBuf = new StringBuilder(40); | ||
68 | for (uint i = 0; ; i++) | ||
69 | { | ||
70 | uint targetSidBufSize = (uint) targetSidBuf.Capacity; | ||
71 | uint ret = NativeMethods.MsiEnumPatchesEx( | ||
72 | targetProductCode, | ||
73 | userSid, | ||
74 | context, | ||
75 | (uint) states, | ||
76 | i, | ||
77 | buf, | ||
78 | targetProductBuf, | ||
79 | out targetContext, | ||
80 | targetSidBuf, | ||
81 | ref targetSidBufSize); | ||
82 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
83 | { | ||
84 | targetSidBuf.Capacity = (int) ++targetSidBufSize; | ||
85 | ret = NativeMethods.MsiEnumPatchesEx( | ||
86 | targetProductCode, | ||
87 | userSid, | ||
88 | context, | ||
89 | (uint) states, | ||
90 | i, | ||
91 | buf, | ||
92 | targetProductBuf, | ||
93 | out targetContext, | ||
94 | targetSidBuf, | ||
95 | ref targetSidBufSize); | ||
96 | } | ||
97 | |||
98 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
99 | { | ||
100 | break; | ||
101 | } | ||
102 | |||
103 | if (ret != 0) | ||
104 | { | ||
105 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
106 | } | ||
107 | |||
108 | string thisPatchCode = buf.ToString(); | ||
109 | if (patchCode == null || patchCode == thisPatchCode) | ||
110 | { | ||
111 | yield return new PatchInstallation( | ||
112 | buf.ToString(), | ||
113 | targetProductBuf.ToString(), | ||
114 | targetSidBuf.ToString(), | ||
115 | targetContext); | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | private string productCode; | ||
121 | |||
122 | /// <summary> | ||
123 | /// Creates a new object for accessing information about a patch installation on the current system. | ||
124 | /// </summary> | ||
125 | /// <param name="patchCode">Patch code (GUID) of the patch.</param> | ||
126 | /// <param name="productCode">ProductCode (GUID) the patch has been applied to. | ||
127 | /// This parameter may be null for patches that are registered only and not yet | ||
128 | /// applied to any product.</param> | ||
129 | /// <remarks><p> | ||
130 | /// All available user contexts will be queried. | ||
131 | /// </p></remarks> | ||
132 | public PatchInstallation(string patchCode, string productCode) | ||
133 | : this(patchCode, productCode, null, UserContexts.All) | ||
134 | { | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Creates a new object for accessing information about a patch installation on the current system. | ||
139 | /// </summary> | ||
140 | /// <param name="patchCode">Registered patch code (GUID) of the patch.</param> | ||
141 | /// <param name="productCode">ProductCode (GUID) the patch has been applied to. | ||
142 | /// This parameter may be null for patches that are registered only and not yet | ||
143 | /// applied to any product.</param> | ||
144 | /// <param name="userSid">The specific user, when working in a user context. This | ||
145 | /// parameter may be null to indicate the current user. The parameter must be null | ||
146 | /// when working in a machine context.</param> | ||
147 | /// <param name="context">The user context. The calling process must have administrative | ||
148 | /// privileges to get information for a product installed for a user other than the | ||
149 | /// current user.</param> | ||
150 | /// <remarks><p> | ||
151 | /// If the <paramref name="productCode"/> is null, the Patch object may | ||
152 | /// only be used to read and update the patch's SourceList information. | ||
153 | /// </p></remarks> | ||
154 | public PatchInstallation(string patchCode, string productCode, string userSid, UserContexts context) | ||
155 | : base(patchCode, userSid, context) | ||
156 | { | ||
157 | if (String.IsNullOrEmpty(patchCode)) | ||
158 | { | ||
159 | throw new ArgumentNullException("patchCode"); | ||
160 | } | ||
161 | |||
162 | this.productCode = productCode; | ||
163 | } | ||
164 | |||
165 | /// <summary> | ||
166 | /// Gets the patch code (GUID) of the patch. | ||
167 | /// </summary> | ||
168 | public string PatchCode | ||
169 | { | ||
170 | get | ||
171 | { | ||
172 | return this.InstallationCode; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | /// <summary> | ||
177 | /// Gets the ProductCode (GUID) of the product. | ||
178 | /// </summary> | ||
179 | public string ProductCode | ||
180 | { | ||
181 | get | ||
182 | { | ||
183 | return this.productCode; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Gets a value indicating whether this patch is currently installed. | ||
189 | /// </summary> | ||
190 | public override bool IsInstalled | ||
191 | { | ||
192 | get | ||
193 | { | ||
194 | return (this.State & PatchStates.Applied) != 0; | ||
195 | } | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Gets a value indicating whether this patch is marked as obsolte. | ||
200 | /// </summary> | ||
201 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")] | ||
202 | public bool IsObsoleted | ||
203 | { | ||
204 | get | ||
205 | { | ||
206 | return (this.State & PatchStates.Obsoleted) != 0; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /// <summary> | ||
211 | /// Gets a value indicating whether this patch is present but has been | ||
212 | /// superseded by a more recent installed patch. | ||
213 | /// </summary> | ||
214 | public bool IsSuperseded | ||
215 | { | ||
216 | get | ||
217 | { | ||
218 | return (this.State & PatchStates.Superseded) != 0; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | internal override int InstallationType | ||
223 | { | ||
224 | get | ||
225 | { | ||
226 | const int MSICODE_PATCH = 0x40000000; | ||
227 | return MSICODE_PATCH; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | /// <summary> | ||
232 | /// Gets the installation state of this instance of the patch. | ||
233 | /// </summary> | ||
234 | /// <exception cref="ArgumentException">An unknown patch was requested</exception> | ||
235 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
236 | public PatchStates State | ||
237 | { | ||
238 | get | ||
239 | { | ||
240 | string stateString = this["State"]; | ||
241 | return (PatchStates) Int32.Parse(stateString, CultureInfo.InvariantCulture.NumberFormat); | ||
242 | } | ||
243 | } | ||
244 | |||
245 | /// <summary> | ||
246 | /// Gets the cached patch file that the product uses. | ||
247 | /// </summary> | ||
248 | public string LocalPackage | ||
249 | { | ||
250 | get | ||
251 | { | ||
252 | return this["LocalPackage"]; | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /// <summary> | ||
257 | /// Gets the set of patch transforms that the last patch | ||
258 | /// installation applied to the product. | ||
259 | /// </summary> | ||
260 | /// <remarks><p> | ||
261 | /// This value may not be available for per-user, non-managed applications | ||
262 | /// if the user is not logged on. | ||
263 | /// </p></remarks> | ||
264 | public string Transforms | ||
265 | { | ||
266 | get | ||
267 | { | ||
268 | // TODO: convert to IList<string>? | ||
269 | return this["Transforms"]; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | /// <summary> | ||
274 | /// Gets the date and time when the patch is applied to the product. | ||
275 | /// </summary> | ||
276 | public DateTime InstallDate | ||
277 | { | ||
278 | get | ||
279 | { | ||
280 | try | ||
281 | { | ||
282 | return DateTime.ParseExact( | ||
283 | this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture); | ||
284 | } | ||
285 | catch (FormatException) | ||
286 | { | ||
287 | return DateTime.MinValue; | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | /// <summary> | ||
293 | /// True patch is marked as possible to uninstall from the product. | ||
294 | /// </summary> | ||
295 | /// <remarks><p> | ||
296 | /// Even if this property is true, the installer can still block the | ||
297 | /// uninstallation if this patch is required by another patch that | ||
298 | /// cannot be uninstalled. | ||
299 | /// </p></remarks> | ||
300 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uninstallable")] | ||
301 | public bool Uninstallable | ||
302 | { | ||
303 | get | ||
304 | { | ||
305 | return this["Uninstallable"] == "1"; | ||
306 | } | ||
307 | } | ||
308 | |||
309 | /// <summary> | ||
310 | /// Get the registered display name for the patch. | ||
311 | /// </summary> | ||
312 | public string DisplayName | ||
313 | { | ||
314 | get | ||
315 | { | ||
316 | return this["DisplayName"]; | ||
317 | } | ||
318 | } | ||
319 | |||
320 | /// <summary> | ||
321 | /// Gets the registered support information URL for the patch. | ||
322 | /// </summary> | ||
323 | public Uri MoreInfoUrl | ||
324 | { | ||
325 | get | ||
326 | { | ||
327 | string value = this["MoreInfoURL"]; | ||
328 | if (!String.IsNullOrEmpty(value)) | ||
329 | { | ||
330 | try | ||
331 | { | ||
332 | return new Uri(value); | ||
333 | } | ||
334 | catch (UriFormatException) { } | ||
335 | } | ||
336 | |||
337 | return null; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | /// <summary> | ||
342 | /// Gets information about a specific patch installation. | ||
343 | /// </summary> | ||
344 | /// <param name="propertyName">The property being retrieved; see remarks for valid properties.</param> | ||
345 | /// <returns>The property value, or an empty string if the property is not set for the patch.</returns> | ||
346 | /// <exception cref="ArgumentOutOfRangeException">An unknown patch or property was requested</exception> | ||
347 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
348 | /// <remarks><p> | ||
349 | /// Win32 MSI APIs: | ||
350 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfo.asp">MsiGetPatchInfo</a>, | ||
351 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfoex.asp">MsiGetPatchInfoEx</a> | ||
352 | /// </p></remarks> | ||
353 | public override string this[string propertyName] | ||
354 | { | ||
355 | get | ||
356 | { | ||
357 | StringBuilder buf = new StringBuilder(""); | ||
358 | uint bufSize = 0; | ||
359 | uint ret; | ||
360 | |||
361 | if (this.Context == UserContexts.UserManaged || | ||
362 | this.Context == UserContexts.UserUnmanaged || | ||
363 | this.Context == UserContexts.Machine) | ||
364 | { | ||
365 | ret = NativeMethods.MsiGetPatchInfoEx( | ||
366 | this.PatchCode, | ||
367 | this.ProductCode, | ||
368 | this.UserSid, | ||
369 | this.Context, | ||
370 | propertyName, | ||
371 | buf, | ||
372 | ref bufSize); | ||
373 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
374 | { | ||
375 | buf.Capacity = (int) ++bufSize; | ||
376 | ret = NativeMethods.MsiGetPatchInfoEx( | ||
377 | this.PatchCode, | ||
378 | this.ProductCode, | ||
379 | this.UserSid, | ||
380 | this.Context, | ||
381 | propertyName, | ||
382 | buf, | ||
383 | ref bufSize); | ||
384 | } | ||
385 | } | ||
386 | else | ||
387 | { | ||
388 | ret = NativeMethods.MsiGetPatchInfo( | ||
389 | this.PatchCode, | ||
390 | propertyName, | ||
391 | buf, | ||
392 | ref bufSize); | ||
393 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
394 | { | ||
395 | buf.Capacity = (int) ++bufSize; | ||
396 | ret = NativeMethods.MsiGetPatchInfo( | ||
397 | this.PatchCode, | ||
398 | propertyName, | ||
399 | buf, | ||
400 | ref bufSize); | ||
401 | } | ||
402 | } | ||
403 | |||
404 | if (ret != 0) | ||
405 | { | ||
406 | return null; | ||
407 | } | ||
408 | |||
409 | return buf.ToString(); | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs new file mode 100644 index 00000000..27739e17 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs | |||
@@ -0,0 +1,801 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Represents a unique instance of a product that | ||
13 | /// is either advertised, installed or unknown. | ||
14 | /// </summary> | ||
15 | public class ProductInstallation : Installation | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Gets the set of all products with a specified upgrade code. This method lists the | ||
19 | /// currently installed and advertised products that have the specified UpgradeCode | ||
20 | /// property in their Property table. | ||
21 | /// </summary> | ||
22 | /// <param name="upgradeCode">Upgrade code of related products</param> | ||
23 | /// <returns>Enumeration of product codes</returns> | ||
24 | /// <remarks><p> | ||
25 | /// Win32 MSI API: | ||
26 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumrelatedproducts.asp">MsiEnumRelatedProducts</a> | ||
27 | /// </p></remarks> | ||
28 | public static IEnumerable<ProductInstallation> GetRelatedProducts(string upgradeCode) | ||
29 | { | ||
30 | StringBuilder buf = new StringBuilder(40); | ||
31 | for (uint i = 0; true; i++) | ||
32 | { | ||
33 | uint ret = NativeMethods.MsiEnumRelatedProducts(upgradeCode, 0, i, buf); | ||
34 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
35 | if (ret != 0) | ||
36 | { | ||
37 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
38 | } | ||
39 | yield return new ProductInstallation(buf.ToString()); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | /// <summary> | ||
44 | /// Enumerates all product installations on the system. | ||
45 | /// </summary> | ||
46 | /// <returns>An enumeration of product objects.</returns> | ||
47 | /// <remarks><p> | ||
48 | /// Win32 MSI API: | ||
49 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproducts.asp">MsiEnumProducts</a>, | ||
50 | /// </p></remarks> | ||
51 | public static IEnumerable<ProductInstallation> AllProducts | ||
52 | { | ||
53 | get | ||
54 | { | ||
55 | return GetProducts(null, null, UserContexts.All); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | /// <summary> | ||
60 | /// Enumerates product installations based on certain criteria. | ||
61 | /// </summary> | ||
62 | /// <param name="productCode">ProductCode (GUID) of the product instances to be enumerated. Only | ||
63 | /// instances of products within the scope of the context specified by the | ||
64 | /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be | ||
65 | /// enumerated. This parameter may be set to null to enumerate all products in the specified | ||
66 | /// context.</param> | ||
67 | /// <param name="userSid">Specifies a security identifier (SID) that restricts the context | ||
68 | /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts | ||
69 | /// enumeration to the current user or any user in the system. The special SID string | ||
70 | /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter | ||
71 | /// can be set to null to restrict the enumeration scope to the current user. When | ||
72 | /// <paramref name="context"/> is set to the machine context only, | ||
73 | /// <paramref name="userSid"/> must be null.</param> | ||
74 | /// <param name="context">Specifies the user context.</param> | ||
75 | /// <returns>An enumeration of product objects for enumerated product instances.</returns> | ||
76 | /// <remarks><p> | ||
77 | /// Win32 MSI API: | ||
78 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproductsex.asp">MsiEnumProductsEx</a> | ||
79 | /// </p></remarks> | ||
80 | public static IEnumerable<ProductInstallation> GetProducts( | ||
81 | string productCode, string userSid, UserContexts context) | ||
82 | { | ||
83 | StringBuilder buf = new StringBuilder(40); | ||
84 | UserContexts targetContext; | ||
85 | StringBuilder targetSidBuf = new StringBuilder(40); | ||
86 | for (uint i = 0; ; i++) | ||
87 | { | ||
88 | uint targetSidBufSize = (uint) targetSidBuf.Capacity; | ||
89 | uint ret = NativeMethods.MsiEnumProductsEx( | ||
90 | productCode, | ||
91 | userSid, | ||
92 | context, | ||
93 | i, | ||
94 | buf, | ||
95 | out targetContext, | ||
96 | targetSidBuf, | ||
97 | ref targetSidBufSize); | ||
98 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
99 | { | ||
100 | targetSidBuf.Capacity = (int) ++targetSidBufSize; | ||
101 | ret = NativeMethods.MsiEnumProductsEx( | ||
102 | productCode, | ||
103 | userSid, | ||
104 | context, | ||
105 | i, | ||
106 | buf, | ||
107 | out targetContext, | ||
108 | targetSidBuf, | ||
109 | ref targetSidBufSize); | ||
110 | } | ||
111 | |||
112 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
113 | { | ||
114 | break; | ||
115 | } | ||
116 | |||
117 | if (ret != 0) | ||
118 | { | ||
119 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
120 | } | ||
121 | |||
122 | yield return new ProductInstallation( | ||
123 | buf.ToString(), | ||
124 | targetSidBuf.ToString(), | ||
125 | targetContext); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | private IDictionary<string, string> properties; | ||
130 | |||
131 | /// <summary> | ||
132 | /// Creates a new object for accessing information about a product installation on the current system. | ||
133 | /// </summary> | ||
134 | /// <param name="productCode">ProductCode (GUID) of the product.</param> | ||
135 | /// <remarks><p> | ||
136 | /// All available user contexts will be queried. | ||
137 | /// </p></remarks> | ||
138 | public ProductInstallation(string productCode) | ||
139 | : this(productCode, null, UserContexts.All) | ||
140 | { | ||
141 | } | ||
142 | |||
143 | /// <summary> | ||
144 | /// Creates a new object for accessing information about a product installation on the current system. | ||
145 | /// </summary> | ||
146 | /// <param name="productCode">ProductCode (GUID) of the product.</param> | ||
147 | /// <param name="userSid">The specific user, when working in a user context. This | ||
148 | /// parameter may be null to indicate the current user. The parameter must be null | ||
149 | /// when working in a machine context.</param> | ||
150 | /// <param name="context">The user context. The calling process must have administrative | ||
151 | /// privileges to get information for a product installed for a user other than the | ||
152 | /// current user.</param> | ||
153 | public ProductInstallation(string productCode, string userSid, UserContexts context) | ||
154 | : base(productCode, userSid, context) | ||
155 | { | ||
156 | if (String.IsNullOrEmpty(productCode)) | ||
157 | { | ||
158 | throw new ArgumentNullException("productCode"); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | internal ProductInstallation(IDictionary<string, string> properties) | ||
163 | : base(properties["ProductCode"], null, UserContexts.None) | ||
164 | { | ||
165 | this.properties = properties; | ||
166 | } | ||
167 | |||
168 | /// <summary> | ||
169 | /// Gets the set of published features for the product. | ||
170 | /// </summary> | ||
171 | /// <returns>Enumeration of published features for the product.</returns> | ||
172 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
173 | /// <remarks><p> | ||
174 | /// Because features are not ordered, any new feature has an arbitrary index, meaning | ||
175 | /// this property can return features in any order. | ||
176 | /// </p><p> | ||
177 | /// Win32 MSI API: | ||
178 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumfeatures.asp">MsiEnumFeatures</a> | ||
179 | /// </p></remarks> | ||
180 | public IEnumerable<FeatureInstallation> Features | ||
181 | { | ||
182 | get | ||
183 | { | ||
184 | StringBuilder buf = new StringBuilder(256); | ||
185 | for (uint i = 0; ; i++) | ||
186 | { | ||
187 | uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, buf, null); | ||
188 | |||
189 | if (ret != 0) | ||
190 | { | ||
191 | break; | ||
192 | } | ||
193 | |||
194 | yield return new FeatureInstallation(buf.ToString(), this.ProductCode); | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | /// <summary> | ||
200 | /// Gets the ProductCode (GUID) of the product. | ||
201 | /// </summary> | ||
202 | public string ProductCode | ||
203 | { | ||
204 | get { return this.InstallationCode; } | ||
205 | } | ||
206 | |||
207 | /// <summary> | ||
208 | /// Gets a value indicating whether this product is installed on the current system. | ||
209 | /// </summary> | ||
210 | public override bool IsInstalled | ||
211 | { | ||
212 | get | ||
213 | { | ||
214 | return (this.State == InstallState.Default); | ||
215 | } | ||
216 | } | ||
217 | |||
218 | /// <summary> | ||
219 | /// Gets a value indicating whether this product is advertised on the current system. | ||
220 | /// </summary> | ||
221 | public bool IsAdvertised | ||
222 | { | ||
223 | get | ||
224 | { | ||
225 | return (this.State == InstallState.Advertised); | ||
226 | } | ||
227 | } | ||
228 | |||
229 | /// <summary> | ||
230 | /// Checks whether the product is installed with elevated privileges. An application is called | ||
231 | /// a "managed application" if elevated (system) privileges are used to install the application. | ||
232 | /// </summary> | ||
233 | /// <returns>True if the product is elevated; false otherwise</returns> | ||
234 | /// <remarks><p> | ||
235 | /// Note that this property does not take into account policies such as AlwaysInstallElevated, | ||
236 | /// but verifies that the local system owns the product's registry data. | ||
237 | /// </p></remarks> | ||
238 | public bool IsElevated | ||
239 | { | ||
240 | get | ||
241 | { | ||
242 | bool isElevated; | ||
243 | uint ret = NativeMethods.MsiIsProductElevated(this.ProductCode, out isElevated); | ||
244 | if (ret != 0) | ||
245 | { | ||
246 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
247 | } | ||
248 | return isElevated; | ||
249 | } | ||
250 | } | ||
251 | |||
252 | /// <summary> | ||
253 | /// Gets the source list of this product installation. | ||
254 | /// </summary> | ||
255 | public override SourceList SourceList | ||
256 | { | ||
257 | get | ||
258 | { | ||
259 | return this.properties == null ? base.SourceList : null; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | internal InstallState State | ||
264 | { | ||
265 | get | ||
266 | { | ||
267 | if (this.properties != null) | ||
268 | { | ||
269 | return InstallState.Unknown; | ||
270 | } | ||
271 | else | ||
272 | { | ||
273 | int installState = NativeMethods.MsiQueryProductState(this.ProductCode); | ||
274 | return (InstallState) installState; | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | internal override int InstallationType | ||
280 | { | ||
281 | get | ||
282 | { | ||
283 | const int MSICODE_PRODUCT = 0x00000000; | ||
284 | return MSICODE_PRODUCT; | ||
285 | } | ||
286 | } | ||
287 | |||
288 | /// <summary> | ||
289 | /// The support link. | ||
290 | /// </summary> | ||
291 | public string HelpLink | ||
292 | { | ||
293 | get | ||
294 | { | ||
295 | return this["HelpLink"]; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | /// <summary> | ||
300 | /// The support telephone. | ||
301 | /// </summary> | ||
302 | public string HelpTelephone | ||
303 | { | ||
304 | get | ||
305 | { | ||
306 | return this["HelpTelephone"]; | ||
307 | } | ||
308 | } | ||
309 | |||
310 | /// <summary> | ||
311 | /// Date and time the product was installed. | ||
312 | /// </summary> | ||
313 | public DateTime InstallDate | ||
314 | { | ||
315 | get | ||
316 | { | ||
317 | try | ||
318 | { | ||
319 | return DateTime.ParseExact( | ||
320 | this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture); | ||
321 | } | ||
322 | catch (FormatException) | ||
323 | { | ||
324 | return DateTime.MinValue; | ||
325 | } | ||
326 | } | ||
327 | } | ||
328 | |||
329 | /// <summary> | ||
330 | /// The installed product name. | ||
331 | /// </summary> | ||
332 | public string ProductName | ||
333 | { | ||
334 | get | ||
335 | { | ||
336 | return this["InstalledProductName"]; | ||
337 | } | ||
338 | } | ||
339 | |||
340 | /// <summary> | ||
341 | /// The installation location. | ||
342 | /// </summary> | ||
343 | public string InstallLocation | ||
344 | { | ||
345 | get | ||
346 | { | ||
347 | return this["InstallLocation"]; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /// <summary> | ||
352 | /// The installation source. | ||
353 | /// </summary> | ||
354 | public string InstallSource | ||
355 | { | ||
356 | get | ||
357 | { | ||
358 | return this["InstallSource"]; | ||
359 | } | ||
360 | } | ||
361 | |||
362 | /// <summary> | ||
363 | /// The local cached package. | ||
364 | /// </summary> | ||
365 | public string LocalPackage | ||
366 | { | ||
367 | get | ||
368 | { | ||
369 | return this["LocalPackage"]; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// The publisher. | ||
375 | /// </summary> | ||
376 | public string Publisher | ||
377 | { | ||
378 | get | ||
379 | { | ||
380 | return this["Publisher"]; | ||
381 | } | ||
382 | } | ||
383 | |||
384 | /// <summary> | ||
385 | /// URL about information. | ||
386 | /// </summary> | ||
387 | public Uri UrlInfoAbout | ||
388 | { | ||
389 | get | ||
390 | { | ||
391 | string value = this["URLInfoAbout"]; | ||
392 | if (!String.IsNullOrEmpty(value)) | ||
393 | { | ||
394 | try | ||
395 | { | ||
396 | return new Uri(value); | ||
397 | } | ||
398 | catch (UriFormatException) { } | ||
399 | } | ||
400 | |||
401 | return null; | ||
402 | } | ||
403 | } | ||
404 | |||
405 | /// <summary> | ||
406 | /// The URL update information. | ||
407 | /// </summary> | ||
408 | public Uri UrlUpdateInfo | ||
409 | { | ||
410 | get | ||
411 | { | ||
412 | string value = this["URLUpdateInfo"]; | ||
413 | if (!String.IsNullOrEmpty(value)) | ||
414 | { | ||
415 | try | ||
416 | { | ||
417 | return new Uri(value); | ||
418 | } | ||
419 | catch (UriFormatException) { } | ||
420 | } | ||
421 | |||
422 | return null; | ||
423 | } | ||
424 | } | ||
425 | |||
426 | /// <summary> | ||
427 | /// The product version. | ||
428 | /// </summary> | ||
429 | public Version ProductVersion | ||
430 | { | ||
431 | get | ||
432 | { | ||
433 | string ver = this["VersionString"]; | ||
434 | return ProductInstallation.ParseVersion(ver); | ||
435 | } | ||
436 | } | ||
437 | |||
438 | /// <summary> | ||
439 | /// The product identifier. | ||
440 | /// </summary> | ||
441 | /// <remarks><p> | ||
442 | /// For more information, see | ||
443 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/productid.asp">ProductID</a> | ||
444 | /// </p></remarks> | ||
445 | public string ProductId | ||
446 | { | ||
447 | get | ||
448 | { | ||
449 | return this["ProductID"]; | ||
450 | } | ||
451 | } | ||
452 | |||
453 | /// <summary> | ||
454 | /// The company that is registered to use the product. | ||
455 | /// </summary> | ||
456 | public string RegCompany | ||
457 | { | ||
458 | get | ||
459 | { | ||
460 | return this["RegCompany"]; | ||
461 | } | ||
462 | } | ||
463 | |||
464 | /// <summary> | ||
465 | /// The owner who is registered to use the product. | ||
466 | /// </summary> | ||
467 | public string RegOwner | ||
468 | { | ||
469 | get | ||
470 | { | ||
471 | return this["RegOwner"]; | ||
472 | } | ||
473 | } | ||
474 | |||
475 | /// <summary> | ||
476 | /// Transforms. | ||
477 | /// </summary> | ||
478 | public string AdvertisedTransforms | ||
479 | { | ||
480 | get | ||
481 | { | ||
482 | return this["Transforms"]; | ||
483 | } | ||
484 | } | ||
485 | |||
486 | /// <summary> | ||
487 | /// Product language. | ||
488 | /// </summary> | ||
489 | public string AdvertisedLanguage | ||
490 | { | ||
491 | get | ||
492 | { | ||
493 | return this["Language"]; | ||
494 | } | ||
495 | } | ||
496 | |||
497 | /// <summary> | ||
498 | /// Human readable product name. | ||
499 | /// </summary> | ||
500 | public string AdvertisedProductName | ||
501 | { | ||
502 | get | ||
503 | { | ||
504 | return this["ProductName"]; | ||
505 | } | ||
506 | } | ||
507 | |||
508 | /// <summary> | ||
509 | /// True if the product is advertised per-machine; | ||
510 | /// false if it is per-user or not advertised. | ||
511 | /// </summary> | ||
512 | public bool AdvertisedPerMachine | ||
513 | { | ||
514 | get | ||
515 | { | ||
516 | return this["AssignmentType"] == "1"; | ||
517 | } | ||
518 | } | ||
519 | |||
520 | /// <summary> | ||
521 | /// Identifier of the package that a product is installed from. | ||
522 | /// </summary> | ||
523 | public string AdvertisedPackageCode | ||
524 | { | ||
525 | get | ||
526 | { | ||
527 | return this["PackageCode"]; | ||
528 | } | ||
529 | } | ||
530 | |||
531 | /// <summary> | ||
532 | /// Version of the advertised product. | ||
533 | /// </summary> | ||
534 | public Version AdvertisedVersion | ||
535 | { | ||
536 | get | ||
537 | { | ||
538 | string ver = this["Version"]; | ||
539 | return ProductInstallation.ParseVersion(ver); | ||
540 | } | ||
541 | } | ||
542 | |||
543 | /// <summary> | ||
544 | /// Primary icon for the package. | ||
545 | /// </summary> | ||
546 | public string AdvertisedProductIcon | ||
547 | { | ||
548 | get | ||
549 | { | ||
550 | return this["ProductIcon"]; | ||
551 | } | ||
552 | } | ||
553 | |||
554 | /// <summary> | ||
555 | /// Name of the installation package for the advertised product. | ||
556 | /// </summary> | ||
557 | public string AdvertisedPackageName | ||
558 | { | ||
559 | get | ||
560 | { | ||
561 | return this["PackageName"]; | ||
562 | } | ||
563 | } | ||
564 | |||
565 | /// <summary> | ||
566 | /// True if the advertised product can be serviced by | ||
567 | /// non-administrators without elevation. | ||
568 | /// </summary> | ||
569 | public bool PrivilegedPatchingAuthorized | ||
570 | { | ||
571 | get | ||
572 | { | ||
573 | return this["AuthorizedLUAApp"] == "1"; | ||
574 | } | ||
575 | } | ||
576 | |||
577 | /// <summary> | ||
578 | /// Gets information about an installation of a product. | ||
579 | /// </summary> | ||
580 | /// <param name="propertyName">Name of the property being retrieved.</param> | ||
581 | /// <exception cref="ArgumentOutOfRangeException">An unknown product or property was requested</exception> | ||
582 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
583 | /// <remarks><p> | ||
584 | /// Win32 MSI APIs: | ||
585 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfo.asp">MsiGetProductInfo</a>, | ||
586 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfoex.asp">MsiGetProductInfoEx</a> | ||
587 | /// </p></remarks> | ||
588 | public override string this[string propertyName] | ||
589 | { | ||
590 | get | ||
591 | { | ||
592 | if (this.properties != null) | ||
593 | { | ||
594 | string value = null; | ||
595 | this.properties.TryGetValue(propertyName, out value); | ||
596 | return value; | ||
597 | } | ||
598 | else | ||
599 | { | ||
600 | StringBuilder buf = new StringBuilder(40); | ||
601 | uint bufSize = (uint) buf.Capacity; | ||
602 | uint ret; | ||
603 | |||
604 | if (this.Context == UserContexts.UserManaged || | ||
605 | this.Context == UserContexts.UserUnmanaged || | ||
606 | this.Context == UserContexts.Machine) | ||
607 | { | ||
608 | ret = NativeMethods.MsiGetProductInfoEx( | ||
609 | this.ProductCode, | ||
610 | this.UserSid, | ||
611 | this.Context, | ||
612 | propertyName, | ||
613 | buf, | ||
614 | ref bufSize); | ||
615 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
616 | { | ||
617 | buf.Capacity = (int) ++bufSize; | ||
618 | ret = NativeMethods.MsiGetProductInfoEx( | ||
619 | this.ProductCode, | ||
620 | this.UserSid, | ||
621 | this.Context, | ||
622 | propertyName, | ||
623 | buf, | ||
624 | ref bufSize); | ||
625 | } | ||
626 | } | ||
627 | else | ||
628 | { | ||
629 | ret = NativeMethods.MsiGetProductInfo( | ||
630 | this.ProductCode, | ||
631 | propertyName, | ||
632 | buf, | ||
633 | ref bufSize); | ||
634 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
635 | { | ||
636 | buf.Capacity = (int) ++bufSize; | ||
637 | ret = NativeMethods.MsiGetProductInfo( | ||
638 | this.ProductCode, | ||
639 | propertyName, | ||
640 | buf, | ||
641 | ref bufSize); | ||
642 | } | ||
643 | } | ||
644 | |||
645 | if (ret != 0) | ||
646 | { | ||
647 | return null; | ||
648 | } | ||
649 | |||
650 | return buf.ToString(); | ||
651 | } | ||
652 | } | ||
653 | } | ||
654 | |||
655 | /// <summary> | ||
656 | /// Gets the installed state for a product feature. | ||
657 | /// </summary> | ||
658 | /// <param name="feature">The feature being queried; identifier from the | ||
659 | /// Feature table</param> | ||
660 | /// <returns>Installation state of the feature for the product instance: either | ||
661 | /// <see cref="InstallState.Local"/>, <see cref="InstallState.Source"/>, | ||
662 | /// or <see cref="InstallState.Advertised"/>.</returns> | ||
663 | /// <remarks><p> | ||
664 | /// Win32 MSI APIs: | ||
665 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>, | ||
666 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestateex.asp">MsiQueryFeatureStateEx</a> | ||
667 | /// </p></remarks> | ||
668 | public InstallState GetFeatureState(string feature) | ||
669 | { | ||
670 | if (this.properties != null) | ||
671 | { | ||
672 | return InstallState.Unknown; | ||
673 | } | ||
674 | else | ||
675 | { | ||
676 | int installState; | ||
677 | uint ret = NativeMethods.MsiQueryFeatureStateEx( | ||
678 | this.ProductCode, | ||
679 | this.UserSid, | ||
680 | this.Context, | ||
681 | feature, | ||
682 | out installState); | ||
683 | if (ret != 0) | ||
684 | { | ||
685 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
686 | } | ||
687 | return (InstallState) installState; | ||
688 | } | ||
689 | } | ||
690 | |||
691 | /// <summary> | ||
692 | /// Gets the installed state for a product component. | ||
693 | /// </summary> | ||
694 | /// <param name="component">The component being queried; GUID of the component | ||
695 | /// as found in the ComponentId column of the Component table.</param> | ||
696 | /// <returns>Installation state of the component for the product instance: either | ||
697 | /// <see cref="InstallState.Local"/> or <see cref="InstallState.Source"/>.</returns> | ||
698 | /// <remarks><p> | ||
699 | /// Win32 MSI API: | ||
700 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiquerycomponnetstate.asp">MsiQueryComponentState</a> | ||
701 | /// </p></remarks> | ||
702 | public InstallState GetComponentState(string component) | ||
703 | { | ||
704 | if (this.properties != null) | ||
705 | { | ||
706 | return InstallState.Unknown; | ||
707 | } | ||
708 | else | ||
709 | { | ||
710 | int installState; | ||
711 | uint ret = NativeMethods.MsiQueryComponentState( | ||
712 | this.ProductCode, | ||
713 | this.UserSid, | ||
714 | this.Context, | ||
715 | component, | ||
716 | out installState); | ||
717 | if (ret != 0) | ||
718 | { | ||
719 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
720 | } | ||
721 | return (InstallState) installState; | ||
722 | } | ||
723 | } | ||
724 | |||
725 | /// <summary> | ||
726 | /// Obtains and stores the user information and product ID from an installation wizard. | ||
727 | /// </summary> | ||
728 | /// <remarks><p> | ||
729 | /// This method is typically called by an application during the first run of the application. The application | ||
730 | /// first gets the <see cref="ProductInstallation.ProductId"/> or <see cref="ProductInstallation.RegOwner"/>. | ||
731 | /// If those properties are missing, the application calls CollectUserInfo. | ||
732 | /// CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects | ||
733 | /// user information. Upon completion of the sequence, user information is registered. Since this API requires | ||
734 | /// an authored user interface, the user interface level should be set to full by calling | ||
735 | /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/> as <see cref="InstallUIOptions.Full"/>. | ||
736 | /// </p><p> | ||
737 | /// The CollectUserInfo method invokes a FirstRun dialog from the product installation database. | ||
738 | /// </p><p> | ||
739 | /// Win32 MSI API: | ||
740 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicollectuserinfo.asp">MsiCollectUserInfo</a> | ||
741 | /// </p></remarks> | ||
742 | public void CollectUserInfo() | ||
743 | { | ||
744 | if (this.properties == null) | ||
745 | { | ||
746 | uint ret = NativeMethods.MsiCollectUserInfo(this.InstallationCode); | ||
747 | if (ret != 0) | ||
748 | { | ||
749 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
750 | } | ||
751 | } | ||
752 | } | ||
753 | |||
754 | /// <summary> | ||
755 | /// Some products might write some invalid/nonstandard version strings to the registry. | ||
756 | /// This method tries to get the best data it can. | ||
757 | /// </summary> | ||
758 | /// <param name="ver">Version string retrieved from the registry.</param> | ||
759 | /// <returns>Version object, or null if the version string is completely invalid.</returns> | ||
760 | private static Version ParseVersion(string ver) | ||
761 | { | ||
762 | if (ver != null) | ||
763 | { | ||
764 | int dotCount = 0; | ||
765 | for (int i = 0; i < ver.Length; i++) | ||
766 | { | ||
767 | char c = ver[i]; | ||
768 | if (c == '.') dotCount++; | ||
769 | else if (!Char.IsDigit(c)) | ||
770 | { | ||
771 | ver = ver.Substring(0, i); | ||
772 | break; | ||
773 | } | ||
774 | } | ||
775 | |||
776 | if (ver.Length > 0) | ||
777 | { | ||
778 | if (dotCount == 0) | ||
779 | { | ||
780 | ver = ver + ".0"; | ||
781 | } | ||
782 | else if (dotCount > 3) | ||
783 | { | ||
784 | string[] verSplit = ver.Split('.'); | ||
785 | ver = String.Join(".", verSplit, 0, 4); | ||
786 | } | ||
787 | |||
788 | try | ||
789 | { | ||
790 | return new Version(ver); | ||
791 | } | ||
792 | catch (ArgumentException) | ||
793 | { | ||
794 | } | ||
795 | } | ||
796 | } | ||
797 | |||
798 | return null; | ||
799 | } | ||
800 | } | ||
801 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs new file mode 100644 index 00000000..2d31fa75 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs | |||
@@ -0,0 +1,1048 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Runtime.InteropServices; | ||
10 | using System.Diagnostics.CodeAnalysis; | ||
11 | |||
12 | /// <summary> | ||
13 | /// The Record object is a container for holding and transferring a variable number of values. | ||
14 | /// Fields within the record are numerically indexed and can contain strings, integers, streams, | ||
15 | /// and null values. Record fields are indexed starting with 1. Field 0 is a special format field. | ||
16 | /// </summary> | ||
17 | /// <remarks><p> | ||
18 | /// Most methods on the Record class have overloads that allow using either a number | ||
19 | /// or a name to designate a field. However note that field names only exist when the | ||
20 | /// Record is directly returned from a query on a database. For other records, attempting | ||
21 | /// to access a field by name will result in an InvalidOperationException. | ||
22 | /// </p></remarks> | ||
23 | public class Record : InstallerHandle | ||
24 | { | ||
25 | private View view; | ||
26 | private bool isFormatStringInvalid; | ||
27 | |||
28 | /// <summary> | ||
29 | /// IsFormatStringInvalid is set from several View methods that invalidate the FormatString | ||
30 | /// and used to determine behavior during Record.ToString(). | ||
31 | /// </summary> | ||
32 | internal protected bool IsFormatStringInvalid | ||
33 | { | ||
34 | set { this.isFormatStringInvalid = value; } | ||
35 | |||
36 | get { return this.isFormatStringInvalid; } | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Creates a new record object with the requested number of fields. | ||
41 | /// </summary> | ||
42 | /// <param name="fieldCount">Required number of fields, which may be 0. | ||
43 | /// The maximum number of fields in a record is limited to 65535.</param> | ||
44 | /// <remarks><p> | ||
45 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
46 | /// It is best that the handle be closed manually as soon as it is no longer | ||
47 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
48 | /// </p><p> | ||
49 | /// Win32 MSI API: | ||
50 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a> | ||
51 | /// </p></remarks> | ||
52 | public Record(int fieldCount) | ||
53 | : this((IntPtr) RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, 0), true, (View) null) | ||
54 | { | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Creates a new record object, providing values for an arbitrary number of fields. | ||
59 | /// </summary> | ||
60 | /// <param name="fields">The values of the record fields. The parameters should be of type Int16, Int32 or String</param> | ||
61 | /// <remarks><p> | ||
62 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
63 | /// It is best that the handle be closed manually as soon as it is no longer | ||
64 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
65 | /// </p><p> | ||
66 | /// Win32 MSI API: | ||
67 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a> | ||
68 | /// </p></remarks> | ||
69 | public Record(params object[] fields) | ||
70 | : this(fields.Length) | ||
71 | { | ||
72 | for (int i = 0; i < fields.Length; i++) | ||
73 | { | ||
74 | this[i + 1] = fields[i]; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | internal Record(IntPtr handle, bool ownsHandle, View view) | ||
79 | : base(handle, ownsHandle) | ||
80 | { | ||
81 | this.view = view; | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Gets the number of fields in a record. | ||
86 | /// </summary> | ||
87 | /// <remarks><p> | ||
88 | /// Win32 MSI API: | ||
89 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetfieldcount.asp">MsiRecordGetFieldCount</a> | ||
90 | /// </p></remarks> | ||
91 | public int FieldCount | ||
92 | { | ||
93 | get | ||
94 | { | ||
95 | return (int) RemotableNativeMethods.MsiRecordGetFieldCount((int) this.Handle); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Gets or sets field 0 of the Record, which is the format string. | ||
101 | /// </summary> | ||
102 | public string FormatString | ||
103 | { | ||
104 | get { return this.GetString(0); } | ||
105 | set { this.SetString(0, value); } | ||
106 | } | ||
107 | |||
108 | /// <summary> | ||
109 | /// Gets or sets a record field value. | ||
110 | /// </summary> | ||
111 | /// <param name="fieldName">Specifies the name of the field of the Record to get or set.</param> | ||
112 | /// <exception cref="ArgumentOutOfRangeException">The name does not match any known field of the Record.</exception> | ||
113 | /// <remarks><p> | ||
114 | /// When getting a field, the type of the object returned depends on the type of the Record field. | ||
115 | /// The object will be one of: Int16, Int32, String, Stream, or null. | ||
116 | /// </p><p> | ||
117 | /// When setting a field, the type of the object provided will be converted to match the View | ||
118 | /// query that returned the record, or if Record was not returned from a view then the type of | ||
119 | /// the object provided will determine the type of the Record field. The object should be one of: | ||
120 | /// Int16, Int32, String, Stream, or null. | ||
121 | /// </p></remarks> | ||
122 | public object this[string fieldName] | ||
123 | { | ||
124 | get | ||
125 | { | ||
126 | int field = this.FindColumn(fieldName); | ||
127 | return this[field]; | ||
128 | } | ||
129 | |||
130 | set | ||
131 | { | ||
132 | int field = this.FindColumn(fieldName); | ||
133 | this[field] = value; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Gets or sets a record field value. | ||
139 | /// </summary> | ||
140 | /// <param name="field">Specifies the field of the Record to get or set.</param> | ||
141 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
142 | /// number of fields in the Record.</exception> | ||
143 | /// <remarks><p> | ||
144 | /// Record fields are indexed starting with 1. Field 0 is a special format field. | ||
145 | /// </p><p> | ||
146 | /// When getting a field, the type of the object returned depends on the type of the Record field. | ||
147 | /// The object will be one of: Int16, Int32, String, Stream, or null. If the Record was returned | ||
148 | /// from a View, the type will match that of the field from the View query. Otherwise, the type | ||
149 | /// will match the type of the last value set for the field. | ||
150 | /// </p><p> | ||
151 | /// When setting a field, the type of the object provided will be converted to match the View | ||
152 | /// query that returned the Record, or if Record was not returned from a View then the type of | ||
153 | /// the object provided will determine the type of the Record field. The object should be one of: | ||
154 | /// Int16, Int32, String, Stream, or null. | ||
155 | /// </p><p> | ||
156 | /// The type-specific getters and setters are slightly more efficient than this property, since | ||
157 | /// they don't have to do the extra work to infer the value's type every time. | ||
158 | /// </p><p> | ||
159 | /// Win32 MSI APIs: | ||
160 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>, | ||
161 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>, | ||
162 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>, | ||
163 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a> | ||
164 | /// </p></remarks> | ||
165 | public object this[int field] | ||
166 | { | ||
167 | get | ||
168 | { | ||
169 | if (field == 0) | ||
170 | { | ||
171 | return this.GetString(0); | ||
172 | } | ||
173 | else | ||
174 | { | ||
175 | Type valueType = null; | ||
176 | if (this.view != null) | ||
177 | { | ||
178 | this.CheckRange(field); | ||
179 | |||
180 | valueType = this.view.Columns[field - 1].Type; | ||
181 | } | ||
182 | |||
183 | if (valueType == null || valueType == typeof(String)) | ||
184 | { | ||
185 | return this.GetString(field); | ||
186 | } | ||
187 | else if (valueType == typeof(Stream)) | ||
188 | { | ||
189 | return this.IsNull(field) ? null : new RecordStream(this, field); | ||
190 | } | ||
191 | else | ||
192 | { | ||
193 | int? value = this.GetNullableInteger(field); | ||
194 | return value.HasValue ? (object) value.Value : null; | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | set | ||
200 | { | ||
201 | if (field == 0) | ||
202 | { | ||
203 | if (value == null) | ||
204 | { | ||
205 | value = String.Empty; | ||
206 | } | ||
207 | |||
208 | this.SetString(0, value.ToString()); | ||
209 | } | ||
210 | else if (value == null) | ||
211 | { | ||
212 | this.SetNullableInteger(field, null); | ||
213 | } | ||
214 | else | ||
215 | { | ||
216 | Type valueType = value.GetType(); | ||
217 | if (valueType == typeof(Int32) || valueType == typeof(Int16)) | ||
218 | { | ||
219 | this.SetInteger(field, (int) value); | ||
220 | } | ||
221 | else if (valueType.IsSubclassOf(typeof(Stream))) | ||
222 | { | ||
223 | this.SetStream(field, (Stream) value); | ||
224 | } | ||
225 | else | ||
226 | { | ||
227 | this.SetString(field, value.ToString()); | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | } | ||
232 | |||
233 | /// <summary> | ||
234 | /// Creates a new Record object from an integer record handle. | ||
235 | /// </summary> | ||
236 | /// <remarks><p> | ||
237 | /// This method is only provided for interop purposes. A Record object | ||
238 | /// should normally be obtained by calling <see cref="WixToolset.Dtf.WindowsInstaller.View.Fetch"/> | ||
239 | /// other methods. | ||
240 | /// <p>The handle will be closed when this object is disposed or finalized.</p> | ||
241 | /// </p></remarks> | ||
242 | /// <param name="handle">Integer record handle</param> | ||
243 | /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param> | ||
244 | public static Record FromHandle(IntPtr handle, bool ownsHandle) | ||
245 | { | ||
246 | return new Record(handle, ownsHandle, (View) null); | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Sets all fields in a record to null. | ||
251 | /// </summary> | ||
252 | /// <remarks><p> | ||
253 | /// Win32 MSI API: | ||
254 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordcleardata.asp">MsiRecordClearData</a> | ||
255 | /// </p></remarks> | ||
256 | public void Clear() | ||
257 | { | ||
258 | uint ret = RemotableNativeMethods.MsiRecordClearData((int) this.Handle); | ||
259 | if (ret != 0) | ||
260 | { | ||
261 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /// <summary> | ||
266 | /// Reports whether a record field is null. | ||
267 | /// </summary> | ||
268 | /// <param name="field">Specifies the field to check.</param> | ||
269 | /// <returns>True if the field is null, false otherwise.</returns> | ||
270 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
271 | /// number of fields in the Record.</exception> | ||
272 | /// <remarks><p> | ||
273 | /// Win32 MSI API: | ||
274 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordisnull.asp">MsiRecordIsNull</a> | ||
275 | /// </p></remarks> | ||
276 | public bool IsNull(int field) | ||
277 | { | ||
278 | this.CheckRange(field); | ||
279 | return RemotableNativeMethods.MsiRecordIsNull((int) this.Handle, (uint) field); | ||
280 | } | ||
281 | |||
282 | /// <summary> | ||
283 | /// Reports whether a record field is null. | ||
284 | /// </summary> | ||
285 | /// <param name="fieldName">Specifies the field to check.</param> | ||
286 | /// <returns>True if the field is null, false otherwise.</returns> | ||
287 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
288 | /// of the named fields in the Record.</exception> | ||
289 | public bool IsNull(string fieldName) | ||
290 | { | ||
291 | int field = this.FindColumn(fieldName); | ||
292 | return this.IsNull(field); | ||
293 | } | ||
294 | |||
295 | /// <summary> | ||
296 | /// Gets the length of a record field. The count does not include the terminating null. | ||
297 | /// </summary> | ||
298 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
299 | /// number of fields in the Record.</exception> | ||
300 | /// <remarks><p> | ||
301 | /// The returned data size is 0 if the field is null, non-existent, | ||
302 | /// or an internal object pointer. The method also returns 0 if the handle is not a valid | ||
303 | /// Record handle. | ||
304 | /// </p><p> | ||
305 | /// If the data is in integer format, the property returns 2 or 4. | ||
306 | /// </p><p> | ||
307 | /// If the data is in string format, the property returns the character count | ||
308 | /// (not including the NULL terminator). | ||
309 | /// </p><p> | ||
310 | /// If the data is in stream format, the property returns the byte count. | ||
311 | /// </p><p> | ||
312 | /// Win32 MSI API: | ||
313 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecorddatasize.asp">MsiRecordDataSize</a> | ||
314 | /// </p></remarks> | ||
315 | public int GetDataSize(int field) | ||
316 | { | ||
317 | this.CheckRange(field); | ||
318 | return (int) RemotableNativeMethods.MsiRecordDataSize((int) this.Handle, (uint) field); | ||
319 | } | ||
320 | |||
321 | /// <summary> | ||
322 | /// Gets the length of a record field. The count does not include the terminating null. | ||
323 | /// </summary> | ||
324 | /// <param name="fieldName">Specifies the field to check.</param> | ||
325 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
326 | /// of the named fields in the Record.</exception> | ||
327 | /// <remarks><p>The returned data size is 0 if the field is null, non-existent, | ||
328 | /// or an internal object pointer. The method also returns 0 if the handle is not a valid | ||
329 | /// Record handle. | ||
330 | /// </p><p> | ||
331 | /// If the data is in integer format, the property returns 2 or 4. | ||
332 | /// </p><p> | ||
333 | /// If the data is in string format, the property returns the character count | ||
334 | /// (not including the NULL terminator). | ||
335 | /// </p><p> | ||
336 | /// If the data is in stream format, the property returns the byte count. | ||
337 | /// </p></remarks> | ||
338 | public int GetDataSize(string fieldName) | ||
339 | { | ||
340 | int field = this.FindColumn(fieldName); | ||
341 | return this.GetDataSize(field); | ||
342 | } | ||
343 | |||
344 | /// <summary> | ||
345 | /// Gets a field value as an integer. | ||
346 | /// </summary> | ||
347 | /// <param name="field">Specifies the field to retrieve.</param> | ||
348 | /// <returns>Integer value of the field, or 0 if the field is null.</returns> | ||
349 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
350 | /// number of fields in the Record.</exception> | ||
351 | /// <remarks><p> | ||
352 | /// Win32 MSI API: | ||
353 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a> | ||
354 | /// </p></remarks> | ||
355 | /// <seealso cref="GetNullableInteger(int)"/> | ||
356 | [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")] | ||
357 | public int GetInteger(int field) | ||
358 | { | ||
359 | this.CheckRange(field); | ||
360 | |||
361 | int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field); | ||
362 | if (value == Int32.MinValue) // MSI_NULL_INTEGER | ||
363 | { | ||
364 | return 0; | ||
365 | } | ||
366 | return value; | ||
367 | } | ||
368 | |||
369 | /// <summary> | ||
370 | /// Gets a field value as an integer. | ||
371 | /// </summary> | ||
372 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
373 | /// <returns>Integer value of the field, or 0 if the field is null.</returns> | ||
374 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
375 | /// of the named fields in the Record.</exception> | ||
376 | /// <seealso cref="GetNullableInteger(string)"/> | ||
377 | [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")] | ||
378 | public int GetInteger(string fieldName) | ||
379 | { | ||
380 | int field = this.FindColumn(fieldName); | ||
381 | return this.GetInteger(field); | ||
382 | } | ||
383 | |||
384 | /// <summary> | ||
385 | /// Gets a field value as an integer. | ||
386 | /// </summary> | ||
387 | /// <param name="field">Specifies the field to retrieve.</param> | ||
388 | /// <returns>Integer value of the field, or null if the field is null.</returns> | ||
389 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
390 | /// number of fields in the Record.</exception> | ||
391 | /// <remarks><p> | ||
392 | /// Win32 MSI API: | ||
393 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a> | ||
394 | /// </p></remarks> | ||
395 | /// <seealso cref="GetInteger(int)"/> | ||
396 | public int? GetNullableInteger(int field) | ||
397 | { | ||
398 | this.CheckRange(field); | ||
399 | |||
400 | int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field); | ||
401 | if (value == Int32.MinValue) // MSI_NULL_INTEGER | ||
402 | { | ||
403 | return null; | ||
404 | } | ||
405 | return value; | ||
406 | } | ||
407 | |||
408 | /// <summary> | ||
409 | /// Gets a field value as an integer. | ||
410 | /// </summary> | ||
411 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
412 | /// <returns>Integer value of the field, or null if the field is null.</returns> | ||
413 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
414 | /// of the named fields in the Record.</exception> | ||
415 | /// <seealso cref="GetInteger(string)"/> | ||
416 | public int? GetNullableInteger(string fieldName) | ||
417 | { | ||
418 | int field = this.FindColumn(fieldName); | ||
419 | return this.GetInteger(field); | ||
420 | } | ||
421 | |||
422 | /// <summary> | ||
423 | /// Sets the value of a field to an integer. | ||
424 | /// </summary> | ||
425 | /// <param name="field">Specifies the field to set.</param> | ||
426 | /// <param name="value">new value of the field</param> | ||
427 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
428 | /// number of fields in the Record.</exception> | ||
429 | /// <remarks><p> | ||
430 | /// Win32 MSI API: | ||
431 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a> | ||
432 | /// </p></remarks> | ||
433 | /// <seealso cref="SetNullableInteger(int,int?)"/> | ||
434 | public void SetInteger(int field, int value) | ||
435 | { | ||
436 | this.CheckRange(field); | ||
437 | |||
438 | uint ret = RemotableNativeMethods.MsiRecordSetInteger((int) this.Handle, (uint) field, value); | ||
439 | if (ret != 0) | ||
440 | { | ||
441 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | /// <summary> | ||
446 | /// Sets the value of a field to an integer. | ||
447 | /// </summary> | ||
448 | /// <param name="fieldName">Specifies the field to set.</param> | ||
449 | /// <param name="value">new value of the field</param> | ||
450 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
451 | /// of the named fields in the Record.</exception> | ||
452 | /// <seealso cref="SetNullableInteger(string,int?)"/> | ||
453 | public void SetInteger(string fieldName, int value) | ||
454 | { | ||
455 | int field = this.FindColumn(fieldName); | ||
456 | this.SetInteger(field, value); | ||
457 | } | ||
458 | |||
459 | /// <summary> | ||
460 | /// Sets the value of a field to a nullable integer. | ||
461 | /// </summary> | ||
462 | /// <param name="field">Specifies the field to set.</param> | ||
463 | /// <param name="value">new value of the field</param> | ||
464 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
465 | /// number of fields in the Record.</exception> | ||
466 | /// <remarks><p> | ||
467 | /// Win32 MSI API: | ||
468 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a> | ||
469 | /// </p></remarks> | ||
470 | /// <seealso cref="SetInteger(int,int)"/> | ||
471 | public void SetNullableInteger(int field, int? value) | ||
472 | { | ||
473 | this.CheckRange(field); | ||
474 | |||
475 | uint ret = RemotableNativeMethods.MsiRecordSetInteger( | ||
476 | (int) this.Handle, | ||
477 | (uint) field, | ||
478 | value.HasValue ? (int) value : Int32.MinValue); | ||
479 | if (ret != 0) | ||
480 | { | ||
481 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
482 | } | ||
483 | } | ||
484 | |||
485 | /// <summary> | ||
486 | /// Sets the value of a field to a nullable integer. | ||
487 | /// </summary> | ||
488 | /// <param name="fieldName">Specifies the field to set.</param> | ||
489 | /// <param name="value">new value of the field</param> | ||
490 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
491 | /// of the named fields in the Record.</exception> | ||
492 | /// <seealso cref="SetInteger(string,int)"/> | ||
493 | public void SetNullableInteger(string fieldName, int? value) | ||
494 | { | ||
495 | int field = this.FindColumn(fieldName); | ||
496 | this.SetNullableInteger(field, value); | ||
497 | } | ||
498 | |||
499 | /// <summary> | ||
500 | /// Gets a field value as a string. | ||
501 | /// </summary> | ||
502 | /// <param name="field">Specifies the field to retrieve.</param> | ||
503 | /// <returns>String value of the field, or an empty string if the field is null.</returns> | ||
504 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
505 | /// number of fields in the Record.</exception> | ||
506 | /// <remarks><p> | ||
507 | /// Win32 MSI API: | ||
508 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a> | ||
509 | /// </p></remarks> | ||
510 | public string GetString(int field) | ||
511 | { | ||
512 | this.CheckRange(field); | ||
513 | |||
514 | StringBuilder buf = new StringBuilder(String.Empty); | ||
515 | uint bufSize = 0; | ||
516 | uint ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize); | ||
517 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
518 | { | ||
519 | buf.Capacity = (int) ++bufSize; | ||
520 | ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize); | ||
521 | } | ||
522 | if (ret != 0) | ||
523 | { | ||
524 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
525 | } | ||
526 | return buf.ToString(); | ||
527 | } | ||
528 | |||
529 | /// <summary> | ||
530 | /// Gets a field value as a string. | ||
531 | /// </summary> | ||
532 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
533 | /// <returns>String value of the field, or an empty string if the field is null.</returns> | ||
534 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
535 | /// of the named fields in the Record.</exception> | ||
536 | public string GetString(string fieldName) | ||
537 | { | ||
538 | int field = this.FindColumn(fieldName); | ||
539 | return this.GetString(field); | ||
540 | } | ||
541 | |||
542 | /// <summary> | ||
543 | /// Sets the value of a field to a string. | ||
544 | /// </summary> | ||
545 | /// <param name="field">Specifies the field to set.</param> | ||
546 | /// <param name="value">new value of the field</param> | ||
547 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
548 | /// number of fields in the Record.</exception> | ||
549 | /// <remarks><p> | ||
550 | /// Win32 MSI API: | ||
551 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a> | ||
552 | /// </p></remarks> | ||
553 | public void SetString(int field, string value) | ||
554 | { | ||
555 | this.CheckRange(field); | ||
556 | |||
557 | if (value == null) | ||
558 | { | ||
559 | value = String.Empty; | ||
560 | } | ||
561 | |||
562 | uint ret = RemotableNativeMethods.MsiRecordSetString((int) this.Handle, (uint) field, value); | ||
563 | if (ret != 0) | ||
564 | { | ||
565 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
566 | } | ||
567 | |||
568 | // If we set the FormatString manually, then it should be valid again | ||
569 | if (field == 0) | ||
570 | { | ||
571 | this.IsFormatStringInvalid = false; | ||
572 | } | ||
573 | } | ||
574 | |||
575 | /// <summary> | ||
576 | /// Sets the value of a field to a string. | ||
577 | /// </summary> | ||
578 | /// <param name="fieldName">Specifies the field to set.</param> | ||
579 | /// <param name="value">new value of the field</param> | ||
580 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
581 | /// of the named fields in the Record.</exception> | ||
582 | public void SetString(string fieldName, string value) | ||
583 | { | ||
584 | int field = this.FindColumn(fieldName); | ||
585 | this.SetString(field, value); | ||
586 | } | ||
587 | |||
588 | /// <summary> | ||
589 | /// Reads a record stream field into a file. | ||
590 | /// </summary> | ||
591 | /// <param name="field">Specifies the field of the Record to get.</param> | ||
592 | /// <param name="filePath">Specifies the path to the file to contain the stream.</param> | ||
593 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
594 | /// number of fields in the Record.</exception> | ||
595 | /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open | ||
596 | /// in read-write mode, or from a database without an associated file path</exception> | ||
597 | /// <remarks><p> | ||
598 | /// This method is capable of directly extracting substorages. To do so, first select both the | ||
599 | /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. | ||
600 | /// However, substorages may only be extracted from a database that is open in read-only mode. | ||
601 | /// </p><p> | ||
602 | /// Win32 MSI API: | ||
603 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a> | ||
604 | /// </p></remarks> | ||
605 | public void GetStream(int field, string filePath) | ||
606 | { | ||
607 | if (String.IsNullOrEmpty(filePath)) | ||
608 | { | ||
609 | throw new ArgumentNullException("filePath"); | ||
610 | } | ||
611 | |||
612 | IList<TableInfo> tables = (this.view != null ? this.view.Tables : null); | ||
613 | if (tables != null && tables.Count == 1 && tables[0].Name == "_Storages" && field == this.FindColumn("Data")) | ||
614 | { | ||
615 | if (!this.view.Database.IsReadOnly) | ||
616 | { | ||
617 | throw new NotSupportedException("Database must be opened read-only to support substorage extraction."); | ||
618 | } | ||
619 | else if (this.view.Database.FilePath == null) | ||
620 | { | ||
621 | throw new NotSupportedException("Database must have an associated file path to support substorage extraction."); | ||
622 | } | ||
623 | else if (this.FindColumn("Name") <= 0) | ||
624 | { | ||
625 | throw new NotSupportedException("Name column must be part of the Record in order to extract substorage."); | ||
626 | } | ||
627 | else | ||
628 | { | ||
629 | Record.ExtractSubStorage(this.view.Database.FilePath, this.GetString("Name"), filePath); | ||
630 | } | ||
631 | } | ||
632 | else | ||
633 | { | ||
634 | if (!this.IsNull(field)) | ||
635 | { | ||
636 | Stream readStream = null, writeStream = null; | ||
637 | try | ||
638 | { | ||
639 | readStream = new RecordStream(this, field); | ||
640 | writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); | ||
641 | int count = 512; | ||
642 | byte[] buf = new byte[count]; | ||
643 | while (count == buf.Length) | ||
644 | { | ||
645 | if ((count = readStream.Read(buf, 0, buf.Length)) > 0) | ||
646 | { | ||
647 | writeStream.Write(buf, 0, count); | ||
648 | } | ||
649 | } | ||
650 | } | ||
651 | finally | ||
652 | { | ||
653 | if (readStream != null) readStream.Close(); | ||
654 | if (writeStream != null) writeStream.Close(); | ||
655 | } | ||
656 | } | ||
657 | } | ||
658 | } | ||
659 | |||
660 | /// <summary> | ||
661 | /// Reads a record stream field into a file. | ||
662 | /// </summary> | ||
663 | /// <param name="fieldName">Specifies the field of the Record to get.</param> | ||
664 | /// <param name="filePath">Specifies the path to the file to contain the stream.</param> | ||
665 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
666 | /// of the named fields in the Record.</exception> | ||
667 | /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open | ||
668 | /// in read-write mode, or from a database without an associated file path</exception> | ||
669 | /// <remarks><p> | ||
670 | /// This method is capable of directly extracting substorages. To do so, first select both the | ||
671 | /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. | ||
672 | /// However, substorages may only be extracted from a database that is open in read-only mode. | ||
673 | /// </p></remarks> | ||
674 | public void GetStream(string fieldName, string filePath) | ||
675 | { | ||
676 | int field = this.FindColumn(fieldName); | ||
677 | this.GetStream(field, filePath); | ||
678 | } | ||
679 | |||
680 | /// <summary> | ||
681 | /// Gets a record stream field. | ||
682 | /// </summary> | ||
683 | /// <param name="field">Specifies the field of the Record to get.</param> | ||
684 | /// <returns>A Stream that reads the field data.</returns> | ||
685 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
686 | /// number of fields in the Record.</exception> | ||
687 | /// <remarks><p> | ||
688 | /// This method is not capable of reading substorages. To extract a substorage, | ||
689 | /// use <see cref="GetStream(int,string)"/>. | ||
690 | /// </p><p> | ||
691 | /// Win32 MSI API: | ||
692 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a> | ||
693 | /// </p></remarks> | ||
694 | public Stream GetStream(int field) | ||
695 | { | ||
696 | this.CheckRange(field); | ||
697 | |||
698 | return this.IsNull(field) ? null : new RecordStream(this, field); | ||
699 | } | ||
700 | |||
701 | /// <summary> | ||
702 | /// Gets a record stream field. | ||
703 | /// </summary> | ||
704 | /// <param name="fieldName">Specifies the field of the Record to get.</param> | ||
705 | /// <returns>A Stream that reads the field data.</returns> | ||
706 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
707 | /// of the named fields in the Record.</exception> | ||
708 | /// <remarks><p> | ||
709 | /// This method is not capable of reading substorages. To extract a substorage, | ||
710 | /// use <see cref="GetStream(string,string)"/>. | ||
711 | /// </p></remarks> | ||
712 | public Stream GetStream(string fieldName) | ||
713 | { | ||
714 | int field = this.FindColumn(fieldName); | ||
715 | return this.GetStream(field); | ||
716 | } | ||
717 | |||
718 | /// <summary> | ||
719 | /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. | ||
720 | /// </summary> | ||
721 | /// <param name="field">Specifies the field of the Record to set.</param> | ||
722 | /// <param name="filePath">Specifies the path to the file containing the stream.</param> | ||
723 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
724 | /// number of fields in the Record.</exception> | ||
725 | /// <remarks><p> | ||
726 | /// The contents of the specified file are read into a stream object. The stream persists if | ||
727 | /// the Record is inserted into the Database and the Database is committed. | ||
728 | /// </p><p> | ||
729 | /// To reset the stream to its beginning you must pass in null for filePath. | ||
730 | /// Do not pass an empty string, "", to reset the stream. | ||
731 | /// </p><p> | ||
732 | /// Setting a stream with this method is more efficient than setting a field to a | ||
733 | /// FileStream object. | ||
734 | /// </p><p> | ||
735 | /// Win32 MSI API: | ||
736 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a> | ||
737 | /// </p></remarks> | ||
738 | public void SetStream(int field, string filePath) | ||
739 | { | ||
740 | this.CheckRange(field); | ||
741 | |||
742 | if (String.IsNullOrEmpty(filePath)) | ||
743 | { | ||
744 | throw new ArgumentNullException("filePath"); | ||
745 | } | ||
746 | |||
747 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, filePath); | ||
748 | if (ret != 0) | ||
749 | { | ||
750 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
751 | } | ||
752 | } | ||
753 | |||
754 | /// <summary> | ||
755 | /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. | ||
756 | /// </summary> | ||
757 | /// <param name="fieldName">Specifies the field name of the Record to set.</param> | ||
758 | /// <param name="filePath">Specifies the path to the file containing the stream.</param> | ||
759 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
760 | /// of the named fields in the Record.</exception> | ||
761 | /// <remarks><p> | ||
762 | /// The contents of the specified file are read into a stream object. The stream persists if | ||
763 | /// the Record is inserted into the Database and the Database is committed. | ||
764 | /// To reset the stream to its beginning you must pass in null for filePath. | ||
765 | /// Do not pass an empty string, "", to reset the stream. | ||
766 | /// </p><p> | ||
767 | /// Setting a stream with this method is more efficient than setting a field to a | ||
768 | /// FileStream object. | ||
769 | /// </p></remarks> | ||
770 | public void SetStream(string fieldName, string filePath) | ||
771 | { | ||
772 | int field = this.FindColumn(fieldName); | ||
773 | this.SetStream(field, filePath); | ||
774 | } | ||
775 | |||
776 | /// <summary> | ||
777 | /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. | ||
778 | /// </summary> | ||
779 | /// <param name="field">Specifies the field of the Record to set.</param> | ||
780 | /// <param name="stream">Specifies the stream data.</param> | ||
781 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
782 | /// number of fields in the Record.</exception> | ||
783 | /// <remarks><p> | ||
784 | /// The stream persists if the Record is inserted into the Database and the Database is committed. | ||
785 | /// </p><p> | ||
786 | /// Win32 MSI API: | ||
787 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a> | ||
788 | /// </p></remarks> | ||
789 | public void SetStream(int field, Stream stream) | ||
790 | { | ||
791 | this.CheckRange(field); | ||
792 | |||
793 | if (stream == null) | ||
794 | { | ||
795 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, null); | ||
796 | if (ret != 0) | ||
797 | { | ||
798 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
799 | } | ||
800 | } | ||
801 | else | ||
802 | { | ||
803 | Stream writeStream = null; | ||
804 | string tempPath = Path.GetTempFileName(); | ||
805 | try | ||
806 | { | ||
807 | writeStream = new FileStream(tempPath, FileMode.Truncate, FileAccess.Write); | ||
808 | byte[] buf = new byte[512]; | ||
809 | int count; | ||
810 | while ((count = stream.Read(buf, 0, buf.Length)) > 0) | ||
811 | { | ||
812 | writeStream.Write(buf, 0, count); | ||
813 | } | ||
814 | writeStream.Close(); | ||
815 | writeStream = null; | ||
816 | |||
817 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, tempPath); | ||
818 | if (ret != 0) | ||
819 | { | ||
820 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
821 | } | ||
822 | } | ||
823 | finally | ||
824 | { | ||
825 | if (writeStream != null) writeStream.Close(); | ||
826 | if (File.Exists(tempPath)) | ||
827 | { | ||
828 | try | ||
829 | { | ||
830 | File.Delete(tempPath); | ||
831 | } | ||
832 | catch (IOException) | ||
833 | { | ||
834 | if (this.view != null) | ||
835 | { | ||
836 | this.view.Database.DeleteOnClose(tempPath); | ||
837 | } | ||
838 | } | ||
839 | } | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | |||
844 | /// <summary> | ||
845 | /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. | ||
846 | /// </summary> | ||
847 | /// <param name="fieldName">Specifies the field name of the Record to set.</param> | ||
848 | /// <param name="stream">Specifies the stream data.</param> | ||
849 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
850 | /// of the named fields in the Record.</exception> | ||
851 | /// <remarks><p> | ||
852 | /// The stream persists if the Record is inserted into the Database and the Database is committed. | ||
853 | /// </p></remarks> | ||
854 | public void SetStream(string fieldName, Stream stream) | ||
855 | { | ||
856 | int field = this.FindColumn(fieldName); | ||
857 | this.SetStream(field, stream); | ||
858 | } | ||
859 | |||
860 | /// <summary> | ||
861 | /// Gets a formatted string representation of the Record. | ||
862 | /// </summary> | ||
863 | /// <returns>A formatted string representation of the Record.</returns> | ||
864 | /// <remarks><p> | ||
865 | /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. | ||
866 | /// </p><p> | ||
867 | /// Win32 MSI API: | ||
868 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
869 | /// </p></remarks> | ||
870 | /// <seealso cref="FormatString"/> | ||
871 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
872 | public override string ToString() | ||
873 | { | ||
874 | return this.ToString((IFormatProvider) null); | ||
875 | } | ||
876 | |||
877 | /// <summary> | ||
878 | /// Gets a formatted string representation of the Record, optionally using a Session to format properties. | ||
879 | /// </summary> | ||
880 | /// <param name="provider">an optional Session instance that will be used to lookup any | ||
881 | /// properties in the Record's format string</param> | ||
882 | /// <returns>A formatted string representation of the Record.</returns> | ||
883 | /// <remarks><p> | ||
884 | /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. | ||
885 | /// </p><p> | ||
886 | /// Win32 MSI API: | ||
887 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
888 | /// </p></remarks> | ||
889 | /// <seealso cref="FormatString"/> | ||
890 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
891 | public string ToString(IFormatProvider provider) | ||
892 | { | ||
893 | if (this.IsFormatStringInvalid) // Format string is invalid | ||
894 | { | ||
895 | // TODO: return all values by default? | ||
896 | return String.Empty; | ||
897 | } | ||
898 | |||
899 | InstallerHandle session = provider as InstallerHandle; | ||
900 | int sessionHandle = session != null ? (int) session.Handle : 0; | ||
901 | StringBuilder buf = new StringBuilder(String.Empty); | ||
902 | uint bufSize = 1; | ||
903 | uint ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize); | ||
904 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
905 | { | ||
906 | bufSize++; | ||
907 | buf = new StringBuilder((int) bufSize); | ||
908 | ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize); | ||
909 | } | ||
910 | if (ret != 0) | ||
911 | { | ||
912 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
913 | } | ||
914 | return buf.ToString(); | ||
915 | } | ||
916 | |||
917 | /// <summary> | ||
918 | /// Gets a formatted string representation of the Record. | ||
919 | /// </summary> | ||
920 | /// <param name="format">String to be used to format the data in the Record, | ||
921 | /// instead of the Record's format string.</param> | ||
922 | /// <returns>A formatted string representation of the Record.</returns> | ||
923 | /// <remarks><p> | ||
924 | /// Win32 MSI API: | ||
925 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
926 | /// </p></remarks> | ||
927 | [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " + | ||
928 | "property separately before calling the ToString() override that takes no parameters.")] | ||
929 | public string ToString(string format) | ||
930 | { | ||
931 | return this.ToString(format, null); | ||
932 | } | ||
933 | |||
934 | /// <summary> | ||
935 | /// Gets a formatted string representation of the Record, optionally using a Session to format properties. | ||
936 | /// </summary> | ||
937 | /// <param name="format">String to be used to format the data in the Record, | ||
938 | /// instead of the Record's format string.</param> | ||
939 | /// <param name="provider">an optional Session instance that will be used to lookup any | ||
940 | /// properties in the Record's format string</param> | ||
941 | /// <returns>A formatted string representation of the Record.</returns> | ||
942 | /// <remarks><p> | ||
943 | /// Win32 MSI API: | ||
944 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
945 | /// </p></remarks> | ||
946 | /// <seealso cref="FormatString"/> | ||
947 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
948 | [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " + | ||
949 | "property separately before calling the ToString() override that takes just a format provider.")] | ||
950 | public string ToString(string format, IFormatProvider provider) | ||
951 | { | ||
952 | if (format == null) | ||
953 | { | ||
954 | return this.ToString(provider); | ||
955 | } | ||
956 | else if (format.Length == 0) | ||
957 | { | ||
958 | return String.Empty; | ||
959 | } | ||
960 | else | ||
961 | { | ||
962 | string savedFormatString = (string) this[0]; | ||
963 | try | ||
964 | { | ||
965 | this.FormatString = format; | ||
966 | return this.ToString(provider); | ||
967 | } | ||
968 | finally | ||
969 | { | ||
970 | this.FormatString = savedFormatString; | ||
971 | } | ||
972 | } | ||
973 | } | ||
974 | |||
975 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
976 | private static void ExtractSubStorage(string databaseFile, string storageName, string extractFile) | ||
977 | { | ||
978 | IStorage storage; | ||
979 | NativeMethods.STGM openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_DENY_WRITE; | ||
980 | int hr = NativeMethods.StgOpenStorage(databaseFile, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0, out storage); | ||
981 | if (hr != 0) | ||
982 | { | ||
983 | Marshal.ThrowExceptionForHR(hr); | ||
984 | } | ||
985 | |||
986 | try | ||
987 | { | ||
988 | openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_EXCLUSIVE; | ||
989 | IStorage subStorage = storage.OpenStorage(storageName, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0); | ||
990 | |||
991 | try | ||
992 | { | ||
993 | IStorage newStorage; | ||
994 | openMode = NativeMethods.STGM.CREATE | NativeMethods.STGM.READWRITE | NativeMethods.STGM.SHARE_EXCLUSIVE; | ||
995 | hr = NativeMethods.StgCreateDocfile(extractFile, (uint) openMode, 0, out newStorage); | ||
996 | if (hr != 0) | ||
997 | { | ||
998 | Marshal.ThrowExceptionForHR(hr); | ||
999 | } | ||
1000 | |||
1001 | try | ||
1002 | { | ||
1003 | subStorage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, newStorage); | ||
1004 | |||
1005 | newStorage.Commit(0); | ||
1006 | } | ||
1007 | finally | ||
1008 | { | ||
1009 | Marshal.ReleaseComObject(newStorage); | ||
1010 | } | ||
1011 | } | ||
1012 | finally | ||
1013 | { | ||
1014 | Marshal.ReleaseComObject(subStorage); | ||
1015 | } | ||
1016 | } | ||
1017 | finally | ||
1018 | { | ||
1019 | Marshal.ReleaseComObject(storage); | ||
1020 | } | ||
1021 | } | ||
1022 | |||
1023 | private int FindColumn(string fieldName) | ||
1024 | { | ||
1025 | if (this.view == null) | ||
1026 | { | ||
1027 | throw new InvalidOperationException(); | ||
1028 | } | ||
1029 | ColumnCollection columns = this.view.Columns; | ||
1030 | for (int i = 0; i < columns.Count; i++) | ||
1031 | { | ||
1032 | if (columns[i].Name == fieldName) | ||
1033 | { | ||
1034 | return i + 1; | ||
1035 | } | ||
1036 | } | ||
1037 | throw new ArgumentOutOfRangeException("fieldName"); | ||
1038 | } | ||
1039 | |||
1040 | private void CheckRange(int field) | ||
1041 | { | ||
1042 | if (field < 0 || field > this.FieldCount) | ||
1043 | { | ||
1044 | throw new ArgumentOutOfRangeException("field"); | ||
1045 | } | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs new file mode 100644 index 00000000..82e8fb46 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs | |||
@@ -0,0 +1,92 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | |||
8 | internal class RecordStream : Stream | ||
9 | { | ||
10 | private Record record; | ||
11 | private int field; | ||
12 | private long position; | ||
13 | |||
14 | internal RecordStream(Record record, int field) | ||
15 | : base() | ||
16 | { | ||
17 | this.record = record; | ||
18 | this.field = field; | ||
19 | } | ||
20 | |||
21 | public override bool CanRead { get { return true; } } | ||
22 | public override bool CanWrite { get { return false; } } | ||
23 | public override bool CanSeek { get { return false; } } | ||
24 | |||
25 | public override long Length | ||
26 | { | ||
27 | get | ||
28 | { | ||
29 | return this.record.GetDataSize(this.field); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | public override long Position | ||
34 | { | ||
35 | get | ||
36 | { | ||
37 | return this.position; | ||
38 | } | ||
39 | |||
40 | set | ||
41 | { | ||
42 | throw new NotSupportedException(); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | public override long Seek(long offset, SeekOrigin origin) | ||
47 | { | ||
48 | throw new NotSupportedException(); | ||
49 | } | ||
50 | |||
51 | public override void SetLength(long value) | ||
52 | { | ||
53 | throw new NotSupportedException(); | ||
54 | } | ||
55 | |||
56 | public override void Flush() | ||
57 | { | ||
58 | throw new NotSupportedException(); | ||
59 | } | ||
60 | |||
61 | public override int Read(byte[] buffer, int offset, int count) | ||
62 | { | ||
63 | if (count > 0) | ||
64 | { | ||
65 | byte[] readBuffer = (offset == 0 ? buffer : new byte[count]); | ||
66 | uint ucount = (uint) count; | ||
67 | uint ret = RemotableNativeMethods.MsiRecordReadStream((int) this.record.Handle, (uint) this.field, buffer, ref ucount); | ||
68 | if (ret != 0) | ||
69 | { | ||
70 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
71 | } | ||
72 | count = (int) ucount; | ||
73 | if (offset > 0) | ||
74 | { | ||
75 | Array.Copy(readBuffer, 0, buffer, offset, count); | ||
76 | } | ||
77 | this.position += count; | ||
78 | } | ||
79 | return count; | ||
80 | } | ||
81 | |||
82 | public override void Write(byte[] array, int offset, int count) | ||
83 | { | ||
84 | throw new NotSupportedException(); | ||
85 | } | ||
86 | |||
87 | public override string ToString() | ||
88 | { | ||
89 | return "[Binary data]"; | ||
90 | } | ||
91 | } | ||
92 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs new file mode 100644 index 00000000..960fd15f --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs | |||
@@ -0,0 +1,1171 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Runtime.InteropServices; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Assigns ID numbers to the MSI APIs that are remotable. | ||
12 | /// </summary> | ||
13 | /// <remarks><p> | ||
14 | /// This enumeration MUST stay in sync with the | ||
15 | /// unmanaged equivalent in RemoteMsiSession.h! | ||
16 | /// </p></remarks> | ||
17 | internal enum RemoteMsiFunctionId | ||
18 | { | ||
19 | EndSession = 0, | ||
20 | MsiCloseHandle, | ||
21 | MsiCreateRecord, | ||
22 | MsiDatabaseGetPrimaryKeys, | ||
23 | MsiDatabaseIsTablePersistent, | ||
24 | MsiDatabaseOpenView, | ||
25 | MsiDoAction, | ||
26 | MsiEnumComponentCosts, | ||
27 | MsiEvaluateCondition, | ||
28 | MsiFormatRecord, | ||
29 | MsiGetActiveDatabase, | ||
30 | MsiGetComponentState, | ||
31 | MsiGetFeatureCost, | ||
32 | MsiGetFeatureState, | ||
33 | MsiGetFeatureValidStates, | ||
34 | MsiGetLanguage, | ||
35 | MsiGetLastErrorRecord, | ||
36 | MsiGetMode, | ||
37 | MsiGetProperty, | ||
38 | MsiGetSourcePath, | ||
39 | MsiGetSummaryInformation, | ||
40 | MsiGetTargetPath, | ||
41 | MsiProcessMessage, | ||
42 | MsiRecordClearData, | ||
43 | MsiRecordDataSize, | ||
44 | MsiRecordGetFieldCount, | ||
45 | MsiRecordGetInteger, | ||
46 | MsiRecordGetString, | ||
47 | MsiRecordIsNull, | ||
48 | MsiRecordReadStream, | ||
49 | MsiRecordSetInteger, | ||
50 | MsiRecordSetStream, | ||
51 | MsiRecordSetString, | ||
52 | MsiSequence, | ||
53 | MsiSetComponentState, | ||
54 | MsiSetFeatureAttributes, | ||
55 | MsiSetFeatureState, | ||
56 | MsiSetInstallLevel, | ||
57 | MsiSetMode, | ||
58 | MsiSetProperty, | ||
59 | MsiSetTargetPath, | ||
60 | MsiSummaryInfoGetProperty, | ||
61 | MsiVerifyDiskSpace, | ||
62 | MsiViewExecute, | ||
63 | MsiViewFetch, | ||
64 | MsiViewGetError, | ||
65 | MsiViewGetColumnInfo, | ||
66 | MsiViewModify, | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Defines the signature of the native function | ||
71 | /// in SfxCA.dll that implements the remoting call. | ||
72 | /// </summary> | ||
73 | internal delegate void MsiRemoteInvoke( | ||
74 | RemoteMsiFunctionId id, | ||
75 | [MarshalAs(UnmanagedType.SysInt)] | ||
76 | IntPtr request, | ||
77 | [MarshalAs(UnmanagedType.SysInt)] | ||
78 | out IntPtr response); | ||
79 | |||
80 | /// <summary> | ||
81 | /// Redirects native API calls to either the normal NativeMethods class | ||
82 | /// or to out-of-proc calls via the remoting channel. | ||
83 | /// </summary> | ||
84 | internal static class RemotableNativeMethods | ||
85 | { | ||
86 | private const int MAX_REQUEST_FIELDS = 4; | ||
87 | private static int requestFieldDataOffset; | ||
88 | private static int requestFieldSize; | ||
89 | |||
90 | [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] | ||
91 | private static IntPtr requestBuf; | ||
92 | |||
93 | private static MsiRemoteInvoke remotingDelegate; | ||
94 | |||
95 | /// <summary> | ||
96 | /// Checks if the current process is using remoting to access the | ||
97 | /// MSI session and database APIs. | ||
98 | /// </summary> | ||
99 | internal static bool RemotingEnabled | ||
100 | { | ||
101 | get | ||
102 | { | ||
103 | return RemotableNativeMethods.remotingDelegate != null; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | /// <summary> | ||
108 | /// Sets a delegate that is used to make remote API calls. | ||
109 | /// </summary> | ||
110 | /// <remarks><p> | ||
111 | /// The implementation of this delegate is provided by the | ||
112 | /// custom action host DLL. | ||
113 | /// </p></remarks> | ||
114 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||
115 | internal static MsiRemoteInvoke RemotingDelegate | ||
116 | { | ||
117 | set | ||
118 | { | ||
119 | RemotableNativeMethods.remotingDelegate = value; | ||
120 | |||
121 | if (value != null && requestBuf == IntPtr.Zero) | ||
122 | { | ||
123 | requestFieldDataOffset = Marshal.SizeOf(typeof(IntPtr)); | ||
124 | requestFieldSize = 2 * Marshal.SizeOf(typeof(IntPtr)); | ||
125 | RemotableNativeMethods.requestBuf = Marshal.AllocHGlobal( | ||
126 | requestFieldSize * MAX_REQUEST_FIELDS); | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | internal static bool IsRemoteHandle(int handle) | ||
132 | { | ||
133 | return (handle & Int32.MinValue) != 0; | ||
134 | } | ||
135 | |||
136 | internal static int MakeRemoteHandle(int handle) | ||
137 | { | ||
138 | if (handle == 0) | ||
139 | { | ||
140 | return handle; | ||
141 | } | ||
142 | |||
143 | if (RemotableNativeMethods.IsRemoteHandle(handle)) | ||
144 | { | ||
145 | throw new InvalidOperationException("Handle already has the remote bit set."); | ||
146 | } | ||
147 | |||
148 | return handle ^ Int32.MinValue; | ||
149 | } | ||
150 | |||
151 | internal static int GetRemoteHandle(int handle) | ||
152 | { | ||
153 | if (handle == 0) | ||
154 | { | ||
155 | return handle; | ||
156 | } | ||
157 | |||
158 | if (!RemotableNativeMethods.IsRemoteHandle(handle)) | ||
159 | { | ||
160 | throw new InvalidOperationException("Handle does not have the remote bit set."); | ||
161 | } | ||
162 | |||
163 | return handle ^ Int32.MinValue; | ||
164 | } | ||
165 | |||
166 | private static void ClearData(IntPtr buf) | ||
167 | { | ||
168 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
169 | { | ||
170 | Marshal.WriteInt32(buf, (i * requestFieldSize), (int) VarEnum.VT_NULL); | ||
171 | Marshal.WriteIntPtr(buf, (i * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | private static void WriteInt(IntPtr buf, int field, int value) | ||
176 | { | ||
177 | Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_I4); | ||
178 | Marshal.WriteInt32(buf, (field * requestFieldSize) + requestFieldDataOffset, value); | ||
179 | } | ||
180 | |||
181 | private static void WriteString(IntPtr buf, int field, string value) | ||
182 | { | ||
183 | if (value == null) | ||
184 | { | ||
185 | Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_NULL); | ||
186 | Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero); | ||
187 | } | ||
188 | else | ||
189 | { | ||
190 | IntPtr stringPtr = Marshal.StringToHGlobalUni(value); | ||
191 | Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_LPWSTR); | ||
192 | Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, stringPtr); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | private static int ReadInt(IntPtr buf, int field) | ||
197 | { | ||
198 | VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize)); | ||
199 | if (vt == VarEnum.VT_EMPTY) | ||
200 | { | ||
201 | return 0; | ||
202 | } | ||
203 | else if (vt != VarEnum.VT_I4 && vt != VarEnum.VT_UI4) | ||
204 | { | ||
205 | throw new InstallerException("Invalid data received from remote MSI function invocation."); | ||
206 | } | ||
207 | return Marshal.ReadInt32(buf, (field * requestFieldSize) + requestFieldDataOffset); | ||
208 | } | ||
209 | |||
210 | private static void ReadString(IntPtr buf, int field, StringBuilder szBuf, ref uint cchBuf) | ||
211 | { | ||
212 | VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize)); | ||
213 | if (vt == VarEnum.VT_NULL) | ||
214 | { | ||
215 | szBuf.Remove(0, szBuf.Length); | ||
216 | return; | ||
217 | } | ||
218 | else if (vt != VarEnum.VT_LPWSTR) | ||
219 | { | ||
220 | throw new InstallerException("Invalid data received from remote MSI function invocation."); | ||
221 | } | ||
222 | |||
223 | szBuf.Remove(0, szBuf.Length); | ||
224 | IntPtr strPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset); | ||
225 | string str = Marshal.PtrToStringUni(strPtr); | ||
226 | if (str != null) | ||
227 | { | ||
228 | szBuf.Append(str); | ||
229 | } | ||
230 | cchBuf = (uint) szBuf.Length; | ||
231 | } | ||
232 | |||
233 | private static void FreeString(IntPtr buf, int field) | ||
234 | { | ||
235 | IntPtr stringPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset); | ||
236 | if (stringPtr != IntPtr.Zero) | ||
237 | { | ||
238 | Marshal.FreeHGlobal(stringPtr); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | private static void ReadStream(IntPtr buf, int field, byte[] sBuf, int count) | ||
243 | { | ||
244 | VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize)); | ||
245 | if (vt != VarEnum.VT_STREAM) | ||
246 | { | ||
247 | throw new InstallerException("Invalid data received from remote MSI function invocation."); | ||
248 | } | ||
249 | |||
250 | IntPtr sPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset); | ||
251 | Marshal.Copy(sPtr, sBuf, 0, count); | ||
252 | } | ||
253 | |||
254 | private static uint MsiFunc_III(RemoteMsiFunctionId id, int in1, int in2, int in3) | ||
255 | { | ||
256 | lock (RemotableNativeMethods.remotingDelegate) | ||
257 | { | ||
258 | ClearData(requestBuf); | ||
259 | WriteInt(requestBuf, 0, in1); | ||
260 | WriteInt(requestBuf, 1, in2); | ||
261 | WriteInt(requestBuf, 2, in3); | ||
262 | IntPtr resp; | ||
263 | remotingDelegate(id, requestBuf, out resp); | ||
264 | return unchecked ((uint) ReadInt(resp, 0)); | ||
265 | } | ||
266 | } | ||
267 | |||
268 | private static uint MsiFunc_IIS(RemoteMsiFunctionId id, int in1, int in2, string in3) | ||
269 | { | ||
270 | lock (RemotableNativeMethods.remotingDelegate) | ||
271 | { | ||
272 | ClearData(requestBuf); | ||
273 | WriteInt(requestBuf, 0, in1); | ||
274 | WriteInt(requestBuf, 1, in2); | ||
275 | WriteString(requestBuf, 2, in3); | ||
276 | IntPtr resp; | ||
277 | remotingDelegate(id, requestBuf, out resp); | ||
278 | FreeString(requestBuf, 2); | ||
279 | return unchecked ((uint) ReadInt(resp, 0)); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private static uint MsiFunc_ISI(RemoteMsiFunctionId id, int in1, string in2, int in3) | ||
284 | { | ||
285 | lock (RemotableNativeMethods.remotingDelegate) | ||
286 | { | ||
287 | ClearData(requestBuf); | ||
288 | WriteInt(requestBuf, 0, in1); | ||
289 | WriteString(requestBuf, 1, in2); | ||
290 | WriteInt(requestBuf, 2, in3); | ||
291 | IntPtr resp; | ||
292 | remotingDelegate(id, requestBuf, out resp); | ||
293 | FreeString(requestBuf, 2); | ||
294 | return unchecked ((uint) ReadInt(resp, 0)); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | private static uint MsiFunc_ISS(RemoteMsiFunctionId id, int in1, string in2, string in3) | ||
299 | { | ||
300 | lock (RemotableNativeMethods.remotingDelegate) | ||
301 | { | ||
302 | ClearData(requestBuf); | ||
303 | WriteInt(requestBuf, 0, in1); | ||
304 | WriteString(requestBuf, 1, in2); | ||
305 | WriteString(requestBuf, 2, in3); | ||
306 | IntPtr resp; | ||
307 | remotingDelegate(id, requestBuf, out resp); | ||
308 | FreeString(requestBuf, 1); | ||
309 | FreeString(requestBuf, 2); | ||
310 | return unchecked ((uint) ReadInt(resp, 0)); | ||
311 | } | ||
312 | } | ||
313 | |||
314 | private static uint MsiFunc_II_I(RemoteMsiFunctionId id, int in1, int in2, out int out1) | ||
315 | { | ||
316 | lock (RemotableNativeMethods.remotingDelegate) | ||
317 | { | ||
318 | ClearData(requestBuf); | ||
319 | WriteInt(requestBuf, 0, in1); | ||
320 | WriteInt(requestBuf, 1, in2); | ||
321 | IntPtr resp; | ||
322 | remotingDelegate(id, requestBuf, out resp); | ||
323 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
324 | out1 = ReadInt(resp, 1); | ||
325 | return ret; | ||
326 | } | ||
327 | } | ||
328 | |||
329 | private static uint MsiFunc_ISII_I(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, out int out1) | ||
330 | { | ||
331 | lock (RemotableNativeMethods.remotingDelegate) | ||
332 | { | ||
333 | ClearData(requestBuf); | ||
334 | WriteInt(requestBuf, 0, in1); | ||
335 | WriteString(requestBuf, 1, in2); | ||
336 | WriteInt(requestBuf, 2, in3); | ||
337 | WriteInt(requestBuf, 3, in4); | ||
338 | IntPtr resp; | ||
339 | remotingDelegate(id, requestBuf, out resp); | ||
340 | FreeString(requestBuf, 1); | ||
341 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
342 | out1 = ReadInt(resp, 1); | ||
343 | return ret; | ||
344 | } | ||
345 | } | ||
346 | |||
347 | private static uint MsiFunc_IS_II(RemoteMsiFunctionId id, int in1, string in2, out int out1, out int out2) | ||
348 | { | ||
349 | lock (RemotableNativeMethods.remotingDelegate) | ||
350 | { | ||
351 | ClearData(requestBuf); | ||
352 | WriteInt(requestBuf, 0, in1); | ||
353 | WriteString(requestBuf, 1, in2); | ||
354 | IntPtr resp; | ||
355 | remotingDelegate(id, requestBuf, out resp); | ||
356 | FreeString(requestBuf, 1); | ||
357 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
358 | out1 = ReadInt(resp, 1); | ||
359 | out2 = ReadInt(resp, 2); | ||
360 | return ret; | ||
361 | } | ||
362 | } | ||
363 | |||
364 | private static uint MsiFunc_II_S(RemoteMsiFunctionId id, int in1, int in2, StringBuilder out1, ref uint cchOut1) | ||
365 | { | ||
366 | lock (RemotableNativeMethods.remotingDelegate) | ||
367 | { | ||
368 | ClearData(requestBuf); | ||
369 | WriteInt(requestBuf, 0, in1); | ||
370 | WriteInt(requestBuf, 1, in2); | ||
371 | IntPtr resp; | ||
372 | remotingDelegate(id, requestBuf, out resp); | ||
373 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
374 | if (ret == 0) ReadString(resp, 1, out1, ref cchOut1); | ||
375 | return ret; | ||
376 | } | ||
377 | } | ||
378 | |||
379 | private static uint MsiFunc_IS_S(RemoteMsiFunctionId id, int in1, string in2, StringBuilder out1, ref uint cchOut1) | ||
380 | { | ||
381 | lock (RemotableNativeMethods.remotingDelegate) | ||
382 | { | ||
383 | ClearData(requestBuf); | ||
384 | WriteInt(requestBuf, 0, in1); | ||
385 | WriteString(requestBuf, 1, in2); | ||
386 | IntPtr resp; | ||
387 | remotingDelegate(id, requestBuf, out resp); | ||
388 | FreeString(requestBuf, 1); | ||
389 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
390 | if (ret == 0) ReadString(resp, 1, out1, ref cchOut1); | ||
391 | return ret; | ||
392 | } | ||
393 | } | ||
394 | |||
395 | private static uint MsiFunc_ISII_SII(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, StringBuilder out1, ref uint cchOut1, out int out2, out int out3) | ||
396 | { | ||
397 | lock (RemotableNativeMethods.remotingDelegate) | ||
398 | { | ||
399 | ClearData(requestBuf); | ||
400 | WriteInt(requestBuf, 0, in1); | ||
401 | WriteString(requestBuf, 1, in2); | ||
402 | WriteInt(requestBuf, 2, in3); | ||
403 | WriteInt(requestBuf, 3, in4); | ||
404 | IntPtr resp; | ||
405 | remotingDelegate(id, requestBuf, out resp); | ||
406 | FreeString(requestBuf, 1); | ||
407 | uint ret = unchecked ((uint) ReadInt(resp, 0)); | ||
408 | if (ret == 0) ReadString(resp, 1, out1, ref cchOut1); | ||
409 | out2 = ReadInt(resp, 2); | ||
410 | out3 = ReadInt(resp, 3); | ||
411 | return ret; | ||
412 | } | ||
413 | } | ||
414 | |||
415 | internal static int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord) | ||
416 | { | ||
417 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
418 | { | ||
419 | return NativeMethods.MsiProcessMessage(hInstall, eMessageType, hRecord); | ||
420 | } | ||
421 | else lock (remotingDelegate) | ||
422 | { | ||
423 | // I don't understand why, but this particular function doesn't work | ||
424 | // when using the static requestBuf -- some data doesn't make it through. | ||
425 | // But it works when a fresh buffer is allocated here every call. | ||
426 | IntPtr buf = Marshal.AllocHGlobal( | ||
427 | requestFieldSize * MAX_REQUEST_FIELDS); | ||
428 | ClearData(buf); | ||
429 | WriteInt(buf, 0, RemotableNativeMethods.GetRemoteHandle(hInstall)); | ||
430 | WriteInt(buf, 1, unchecked ((int) eMessageType)); | ||
431 | WriteInt(buf, 2, RemotableNativeMethods.GetRemoteHandle(hRecord)); | ||
432 | IntPtr resp; | ||
433 | remotingDelegate(RemoteMsiFunctionId.MsiProcessMessage, buf, out resp); | ||
434 | Marshal.FreeHGlobal(buf); | ||
435 | return ReadInt(resp, 0); | ||
436 | } | ||
437 | } | ||
438 | |||
439 | internal static uint MsiCloseHandle(int hAny) | ||
440 | { | ||
441 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hAny)) | ||
442 | return NativeMethods.MsiCloseHandle(hAny); | ||
443 | else | ||
444 | return RemotableNativeMethods.MsiFunc_III( | ||
445 | RemoteMsiFunctionId.MsiCloseHandle, RemotableNativeMethods.GetRemoteHandle(hAny), 0, 0); | ||
446 | } | ||
447 | |||
448 | internal static uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf) | ||
449 | { | ||
450 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
451 | return NativeMethods.MsiGetProperty(hInstall, szName, szValueBuf, ref cchValueBuf); | ||
452 | else | ||
453 | { | ||
454 | return RemotableNativeMethods.MsiFunc_IS_S( | ||
455 | RemoteMsiFunctionId.MsiGetProperty, | ||
456 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
457 | szName, | ||
458 | szValueBuf, | ||
459 | ref cchValueBuf); | ||
460 | } | ||
461 | } | ||
462 | |||
463 | internal static uint MsiSetProperty(int hInstall, string szName, string szValue) | ||
464 | { | ||
465 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
466 | return NativeMethods.MsiSetProperty(hInstall, szName, szValue); | ||
467 | else | ||
468 | { | ||
469 | return RemotableNativeMethods.MsiFunc_ISS( | ||
470 | RemoteMsiFunctionId.MsiSetProperty, | ||
471 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
472 | szName, | ||
473 | szValue); | ||
474 | } | ||
475 | } | ||
476 | |||
477 | internal static int MsiCreateRecord(uint cParams, int hAny) | ||
478 | { | ||
479 | // When remoting is enabled, we might need to create either a local or | ||
480 | // remote record, depending on the handle it is to have an affinity with. | ||
481 | // If no affinity handle is specified, create a remote record (the 99% case). | ||
482 | if (!RemotingEnabled || | ||
483 | (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny))) | ||
484 | { | ||
485 | return NativeMethods.MsiCreateRecord(cParams); | ||
486 | } | ||
487 | else | ||
488 | { | ||
489 | int hRecord = unchecked((int)RemotableNativeMethods.MsiFunc_III( | ||
490 | RemoteMsiFunctionId.MsiCreateRecord, (int) cParams, 0, 0)); | ||
491 | return RemotableNativeMethods.MakeRemoteHandle(hRecord); | ||
492 | } | ||
493 | } | ||
494 | |||
495 | internal static uint MsiRecordGetFieldCount(int hRecord) | ||
496 | { | ||
497 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
498 | return NativeMethods.MsiRecordGetFieldCount(hRecord); | ||
499 | else | ||
500 | { | ||
501 | return RemotableNativeMethods.MsiFunc_III( | ||
502 | RemoteMsiFunctionId.MsiRecordGetFieldCount, | ||
503 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
504 | 0, | ||
505 | 0); | ||
506 | } | ||
507 | } | ||
508 | |||
509 | internal static int MsiRecordGetInteger(int hRecord, uint iField) | ||
510 | { | ||
511 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
512 | return NativeMethods.MsiRecordGetInteger(hRecord, iField); | ||
513 | else | ||
514 | { | ||
515 | return unchecked ((int) RemotableNativeMethods.MsiFunc_III( | ||
516 | RemoteMsiFunctionId.MsiRecordGetInteger, | ||
517 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
518 | (int) iField, | ||
519 | 0)); | ||
520 | } | ||
521 | } | ||
522 | |||
523 | internal static uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf) | ||
524 | { | ||
525 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
526 | { | ||
527 | return NativeMethods.MsiRecordGetString(hRecord, iField, szValueBuf, ref cchValueBuf); | ||
528 | } | ||
529 | else | ||
530 | { | ||
531 | return RemotableNativeMethods.MsiFunc_II_S( | ||
532 | RemoteMsiFunctionId.MsiRecordGetString, | ||
533 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
534 | (int) iField, | ||
535 | szValueBuf, | ||
536 | ref cchValueBuf); | ||
537 | } | ||
538 | } | ||
539 | |||
540 | internal static uint MsiRecordSetInteger(int hRecord, uint iField, int iValue) | ||
541 | { | ||
542 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
543 | return NativeMethods.MsiRecordSetInteger(hRecord, iField, iValue); | ||
544 | else | ||
545 | { | ||
546 | return RemotableNativeMethods.MsiFunc_III( | ||
547 | RemoteMsiFunctionId.MsiRecordSetInteger, | ||
548 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
549 | (int) iField, | ||
550 | iValue); | ||
551 | } | ||
552 | } | ||
553 | |||
554 | internal static uint MsiRecordSetString(int hRecord, uint iField, string szValue) | ||
555 | { | ||
556 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
557 | return NativeMethods.MsiRecordSetString(hRecord, iField, szValue); | ||
558 | else | ||
559 | { | ||
560 | return RemotableNativeMethods.MsiFunc_IIS( | ||
561 | RemoteMsiFunctionId.MsiRecordSetString, | ||
562 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
563 | (int) iField, | ||
564 | szValue); | ||
565 | } | ||
566 | } | ||
567 | |||
568 | internal static int MsiGetActiveDatabase(int hInstall) | ||
569 | { | ||
570 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
571 | return NativeMethods.MsiGetActiveDatabase(hInstall); | ||
572 | else | ||
573 | { | ||
574 | int hDatabase = (int)RemotableNativeMethods.MsiFunc_III( | ||
575 | RemoteMsiFunctionId.MsiGetActiveDatabase, | ||
576 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
577 | 0, | ||
578 | 0); | ||
579 | return RemotableNativeMethods.MakeRemoteHandle(hDatabase); | ||
580 | } | ||
581 | } | ||
582 | |||
583 | internal static uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView) | ||
584 | { | ||
585 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase)) | ||
586 | return NativeMethods.MsiDatabaseOpenView(hDatabase, szQuery, out hView); | ||
587 | else | ||
588 | { | ||
589 | uint err = RemotableNativeMethods.MsiFunc_ISII_I( | ||
590 | RemoteMsiFunctionId.MsiDatabaseOpenView, | ||
591 | RemotableNativeMethods.GetRemoteHandle(hDatabase), | ||
592 | szQuery, | ||
593 | 0, | ||
594 | 0, | ||
595 | out hView); | ||
596 | hView = RemotableNativeMethods.MakeRemoteHandle(hView); | ||
597 | return err; | ||
598 | } | ||
599 | } | ||
600 | |||
601 | internal static uint MsiViewExecute(int hView, int hRecord) | ||
602 | { | ||
603 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView)) | ||
604 | return NativeMethods.MsiViewExecute(hView, hRecord); | ||
605 | else | ||
606 | { | ||
607 | return RemotableNativeMethods.MsiFunc_III( | ||
608 | RemoteMsiFunctionId.MsiViewExecute, | ||
609 | RemotableNativeMethods.GetRemoteHandle(hView), | ||
610 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
611 | 0); | ||
612 | } | ||
613 | } | ||
614 | |||
615 | internal static uint MsiViewFetch(int hView, out int hRecord) | ||
616 | { | ||
617 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView)) | ||
618 | return NativeMethods.MsiViewFetch(hView, out hRecord); | ||
619 | else | ||
620 | { | ||
621 | uint err = RemotableNativeMethods.MsiFunc_II_I( | ||
622 | RemoteMsiFunctionId.MsiViewFetch, | ||
623 | RemotableNativeMethods.GetRemoteHandle(hView), | ||
624 | 0, | ||
625 | out hRecord); | ||
626 | hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord); | ||
627 | return err; | ||
628 | } | ||
629 | } | ||
630 | |||
631 | internal static uint MsiViewModify(int hView, int iModifyMode, int hRecord) | ||
632 | { | ||
633 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView)) | ||
634 | return NativeMethods.MsiViewModify(hView, iModifyMode, hRecord); | ||
635 | else | ||
636 | { | ||
637 | return RemotableNativeMethods.MsiFunc_III( | ||
638 | RemoteMsiFunctionId.MsiViewModify, | ||
639 | RemotableNativeMethods.GetRemoteHandle(hView), | ||
640 | iModifyMode, | ||
641 | RemotableNativeMethods.GetRemoteHandle(hRecord)); | ||
642 | } | ||
643 | } | ||
644 | |||
645 | internal static int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf) | ||
646 | { | ||
647 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView)) | ||
648 | return NativeMethods.MsiViewGetError(hView, szColumnNameBuffer, ref cchBuf); | ||
649 | else | ||
650 | { | ||
651 | return unchecked ((int) RemotableNativeMethods.MsiFunc_II_S( | ||
652 | RemoteMsiFunctionId.MsiViewGetError, | ||
653 | RemotableNativeMethods.GetRemoteHandle(hView), | ||
654 | 0, | ||
655 | szColumnNameBuffer, | ||
656 | ref cchBuf)); | ||
657 | } | ||
658 | } | ||
659 | |||
660 | internal static uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord) | ||
661 | { | ||
662 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView)) | ||
663 | return NativeMethods.MsiViewGetColumnInfo(hView, eColumnInfo, out hRecord); | ||
664 | else | ||
665 | { | ||
666 | uint err = RemotableNativeMethods.MsiFunc_II_I( | ||
667 | RemoteMsiFunctionId.MsiViewGetColumnInfo, | ||
668 | RemotableNativeMethods.GetRemoteHandle(hView), | ||
669 | (int) eColumnInfo, | ||
670 | out hRecord); | ||
671 | hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord); | ||
672 | return err; | ||
673 | } | ||
674 | } | ||
675 | |||
676 | internal static uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf) | ||
677 | { | ||
678 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
679 | return NativeMethods.MsiFormatRecord(hInstall, hRecord, szResultBuf, ref cchResultBuf); | ||
680 | else | ||
681 | { | ||
682 | return RemotableNativeMethods.MsiFunc_II_S( | ||
683 | RemoteMsiFunctionId.MsiFormatRecord, | ||
684 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
685 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
686 | szResultBuf, | ||
687 | ref cchResultBuf); | ||
688 | } | ||
689 | } | ||
690 | |||
691 | internal static uint MsiRecordClearData(int hRecord) | ||
692 | { | ||
693 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
694 | return NativeMethods.MsiRecordClearData(hRecord); | ||
695 | else | ||
696 | { | ||
697 | return RemotableNativeMethods.MsiFunc_III( | ||
698 | RemoteMsiFunctionId.MsiRecordClearData, | ||
699 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
700 | 0, | ||
701 | 0); | ||
702 | } | ||
703 | } | ||
704 | |||
705 | internal static bool MsiRecordIsNull(int hRecord, uint iField) | ||
706 | { | ||
707 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
708 | return NativeMethods.MsiRecordIsNull(hRecord, iField); | ||
709 | else | ||
710 | { | ||
711 | return 0 != RemotableNativeMethods.MsiFunc_III( | ||
712 | RemoteMsiFunctionId.MsiRecordIsNull, | ||
713 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
714 | (int) iField, | ||
715 | 0); | ||
716 | } | ||
717 | } | ||
718 | |||
719 | internal static uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord) | ||
720 | { | ||
721 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase)) | ||
722 | return NativeMethods.MsiDatabaseGetPrimaryKeys(hDatabase, szTableName, out hRecord); | ||
723 | else | ||
724 | { | ||
725 | uint err = RemotableNativeMethods.MsiFunc_ISII_I( | ||
726 | RemoteMsiFunctionId.MsiDatabaseGetPrimaryKeys, | ||
727 | RemotableNativeMethods.GetRemoteHandle(hDatabase), | ||
728 | szTableName, | ||
729 | 0, | ||
730 | 0, | ||
731 | out hRecord); | ||
732 | hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord); | ||
733 | return err; | ||
734 | } | ||
735 | } | ||
736 | |||
737 | internal static uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName) | ||
738 | { | ||
739 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase)) | ||
740 | return NativeMethods.MsiDatabaseIsTablePersistent(hDatabase, szTableName); | ||
741 | else | ||
742 | { | ||
743 | return RemotableNativeMethods.MsiFunc_ISI( | ||
744 | RemoteMsiFunctionId.MsiDatabaseIsTablePersistent, | ||
745 | RemotableNativeMethods.GetRemoteHandle(hDatabase), | ||
746 | szTableName, | ||
747 | 0); | ||
748 | } | ||
749 | } | ||
750 | |||
751 | internal static uint MsiDoAction(int hInstall, string szAction) | ||
752 | { | ||
753 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
754 | return NativeMethods.MsiDoAction(hInstall, szAction); | ||
755 | else | ||
756 | { | ||
757 | return RemotableNativeMethods.MsiFunc_ISI( | ||
758 | RemoteMsiFunctionId.MsiDoAction, | ||
759 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
760 | szAction, | ||
761 | 0); | ||
762 | } | ||
763 | } | ||
764 | |||
765 | internal static uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost) | ||
766 | { | ||
767 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
768 | return NativeMethods.MsiEnumComponentCosts(hInstall, szComponent, dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost); | ||
769 | else | ||
770 | { | ||
771 | return RemotableNativeMethods.MsiFunc_ISII_SII( | ||
772 | RemoteMsiFunctionId.MsiEvaluateCondition, | ||
773 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
774 | szComponent, (int) dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost); | ||
775 | } | ||
776 | } | ||
777 | |||
778 | internal static uint MsiEvaluateCondition(int hInstall, string szCondition) | ||
779 | { | ||
780 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
781 | return NativeMethods.MsiEvaluateCondition(hInstall, szCondition); | ||
782 | else | ||
783 | { | ||
784 | return RemotableNativeMethods.MsiFunc_ISI( | ||
785 | RemoteMsiFunctionId.MsiEvaluateCondition, | ||
786 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
787 | szCondition, | ||
788 | 0); | ||
789 | } | ||
790 | } | ||
791 | |||
792 | internal static uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction) | ||
793 | { | ||
794 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
795 | return NativeMethods.MsiGetComponentState(hInstall, szComponent, out iInstalled, out iAction); | ||
796 | else | ||
797 | { | ||
798 | return RemotableNativeMethods.MsiFunc_IS_II( | ||
799 | RemoteMsiFunctionId.MsiGetComponentState, | ||
800 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
801 | szComponent, | ||
802 | out iInstalled, | ||
803 | out iAction); | ||
804 | } | ||
805 | } | ||
806 | |||
807 | internal static uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost) | ||
808 | { | ||
809 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
810 | return NativeMethods.MsiGetFeatureCost(hInstall, szFeature, iCostTree, iState, out iCost); | ||
811 | else | ||
812 | { | ||
813 | return RemotableNativeMethods.MsiFunc_ISII_I( | ||
814 | RemoteMsiFunctionId.MsiGetFeatureCost, | ||
815 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
816 | szFeature, | ||
817 | iCostTree, | ||
818 | iState, | ||
819 | out iCost); | ||
820 | } | ||
821 | } | ||
822 | |||
823 | internal static uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction) | ||
824 | { | ||
825 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
826 | return NativeMethods.MsiGetFeatureState(hInstall, szFeature, out iInstalled, out iAction); | ||
827 | else | ||
828 | { | ||
829 | return RemotableNativeMethods.MsiFunc_IS_II( | ||
830 | RemoteMsiFunctionId.MsiGetFeatureState, | ||
831 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
832 | szFeature, | ||
833 | out iInstalled, | ||
834 | out iAction); | ||
835 | } | ||
836 | } | ||
837 | |||
838 | internal static uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstalledState) | ||
839 | { | ||
840 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
841 | return NativeMethods.MsiGetFeatureValidStates(hInstall, szFeature, out dwInstalledState); | ||
842 | else | ||
843 | { | ||
844 | int iTemp; | ||
845 | uint ret = RemotableNativeMethods.MsiFunc_ISII_I( | ||
846 | RemoteMsiFunctionId.MsiGetFeatureValidStates, | ||
847 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
848 | szFeature, | ||
849 | 0, | ||
850 | 0, | ||
851 | out iTemp); | ||
852 | dwInstalledState = (uint) iTemp; | ||
853 | return ret; | ||
854 | } | ||
855 | } | ||
856 | |||
857 | internal static int MsiGetLanguage(int hInstall) | ||
858 | { | ||
859 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
860 | return NativeMethods.MsiGetLanguage(hInstall); | ||
861 | else | ||
862 | { | ||
863 | return unchecked((int)RemotableNativeMethods.MsiFunc_III( | ||
864 | RemoteMsiFunctionId.MsiGetLanguage, | ||
865 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
866 | 0, | ||
867 | 0)); | ||
868 | } | ||
869 | } | ||
870 | |||
871 | internal static int MsiGetLastErrorRecord(int hAny) | ||
872 | { | ||
873 | // When remoting is enabled, we might need to create either a local or | ||
874 | // remote record, depending on the handle it is to have an affinity with. | ||
875 | // If no affinity handle is specified, create a remote record (the 99% case). | ||
876 | if (!RemotingEnabled || | ||
877 | (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny))) | ||
878 | { | ||
879 | return NativeMethods.MsiGetLastErrorRecord(); | ||
880 | } | ||
881 | else | ||
882 | { | ||
883 | int hRecord = unchecked((int) RemotableNativeMethods.MsiFunc_III( | ||
884 | RemoteMsiFunctionId.MsiGetLastErrorRecord, 0, 0, 0)); | ||
885 | return RemotableNativeMethods.MakeRemoteHandle(hRecord); | ||
886 | } | ||
887 | } | ||
888 | |||
889 | internal static bool MsiGetMode(int hInstall, uint iRunMode) | ||
890 | { | ||
891 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
892 | return NativeMethods.MsiGetMode(hInstall, iRunMode); | ||
893 | else | ||
894 | { | ||
895 | return 0 != RemotableNativeMethods.MsiFunc_III( | ||
896 | RemoteMsiFunctionId.MsiGetMode, | ||
897 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
898 | (int) iRunMode, | ||
899 | 0); | ||
900 | } | ||
901 | } | ||
902 | |||
903 | internal static uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf) | ||
904 | { | ||
905 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
906 | return NativeMethods.MsiGetSourcePath(hInstall, szFolder, szPathBuf, ref cchPathBuf); | ||
907 | else | ||
908 | { | ||
909 | return RemotableNativeMethods.MsiFunc_IS_S( | ||
910 | RemoteMsiFunctionId.MsiGetSourcePath, | ||
911 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
912 | szFolder, | ||
913 | szPathBuf, | ||
914 | ref cchPathBuf); | ||
915 | } | ||
916 | } | ||
917 | |||
918 | internal static uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo) | ||
919 | { | ||
920 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase)) | ||
921 | return NativeMethods.MsiGetSummaryInformation(hDatabase, szDatabasePath, uiUpdateCount, out hSummaryInfo); | ||
922 | else | ||
923 | { | ||
924 | uint err = RemotableNativeMethods.MsiFunc_ISII_I( | ||
925 | RemoteMsiFunctionId.MsiGetSummaryInformation, | ||
926 | RemotableNativeMethods.GetRemoteHandle(hDatabase), | ||
927 | szDatabasePath, | ||
928 | (int)uiUpdateCount, | ||
929 | 0, | ||
930 | out hSummaryInfo); | ||
931 | hSummaryInfo = RemotableNativeMethods.MakeRemoteHandle(hSummaryInfo); | ||
932 | return err; | ||
933 | } | ||
934 | } | ||
935 | |||
936 | internal static uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf) | ||
937 | { | ||
938 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
939 | return NativeMethods.MsiGetTargetPath(hInstall, szFolder, szPathBuf, ref cchPathBuf); | ||
940 | else | ||
941 | { | ||
942 | return RemotableNativeMethods.MsiFunc_IS_S( | ||
943 | RemoteMsiFunctionId.MsiGetTargetPath, | ||
944 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
945 | szFolder, | ||
946 | szPathBuf, | ||
947 | ref cchPathBuf); | ||
948 | } | ||
949 | } | ||
950 | |||
951 | internal static uint MsiRecordDataSize(int hRecord, uint iField) | ||
952 | { | ||
953 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
954 | return NativeMethods.MsiRecordDataSize(hRecord, iField); | ||
955 | else | ||
956 | { | ||
957 | return RemotableNativeMethods.MsiFunc_III( | ||
958 | RemoteMsiFunctionId.MsiRecordDataSize, | ||
959 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
960 | (int) iField, 0); | ||
961 | } | ||
962 | } | ||
963 | |||
964 | internal static uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf) | ||
965 | { | ||
966 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
967 | { | ||
968 | return NativeMethods.MsiRecordReadStream(hRecord, iField, szDataBuf, ref cbDataBuf); | ||
969 | } | ||
970 | else lock (RemotableNativeMethods.remotingDelegate) | ||
971 | { | ||
972 | ClearData(requestBuf); | ||
973 | unchecked | ||
974 | { | ||
975 | WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hRecord)); | ||
976 | WriteInt(requestBuf, 1, (int) iField); | ||
977 | WriteInt(requestBuf, 2, (int) cbDataBuf); | ||
978 | IntPtr resp; | ||
979 | remotingDelegate(RemoteMsiFunctionId.MsiRecordReadStream, requestBuf, out resp); | ||
980 | uint ret = (uint) ReadInt(resp, 0); | ||
981 | if (ret == 0) | ||
982 | { | ||
983 | cbDataBuf = (uint) ReadInt(resp, 2); | ||
984 | if (cbDataBuf > 0) | ||
985 | { | ||
986 | RemotableNativeMethods.ReadStream(resp, 1, szDataBuf, (int) cbDataBuf); | ||
987 | } | ||
988 | } | ||
989 | return ret; | ||
990 | } | ||
991 | } | ||
992 | } | ||
993 | |||
994 | internal static uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath) | ||
995 | { | ||
996 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord)) | ||
997 | return NativeMethods.MsiRecordSetStream(hRecord, iField, szFilePath); | ||
998 | else | ||
999 | { | ||
1000 | return RemotableNativeMethods.MsiFunc_IIS( | ||
1001 | RemoteMsiFunctionId.MsiRecordSetStream, | ||
1002 | RemotableNativeMethods.GetRemoteHandle(hRecord), | ||
1003 | (int) iField, | ||
1004 | szFilePath); | ||
1005 | } | ||
1006 | } | ||
1007 | |||
1008 | internal static uint MsiSequence(int hInstall, string szTable, int iSequenceMode) | ||
1009 | { | ||
1010 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1011 | return NativeMethods.MsiSequence(hInstall, szTable, iSequenceMode); | ||
1012 | else | ||
1013 | { | ||
1014 | return RemotableNativeMethods.MsiFunc_ISI( | ||
1015 | RemoteMsiFunctionId.MsiSequence, | ||
1016 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1017 | szTable, | ||
1018 | iSequenceMode); | ||
1019 | } | ||
1020 | } | ||
1021 | |||
1022 | internal static uint MsiSetComponentState(int hInstall, string szComponent, int iState) | ||
1023 | { | ||
1024 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1025 | return NativeMethods.MsiSetComponentState(hInstall, szComponent, iState); | ||
1026 | else | ||
1027 | { | ||
1028 | return RemotableNativeMethods.MsiFunc_ISI( | ||
1029 | RemoteMsiFunctionId.MsiSetComponentState, | ||
1030 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1031 | szComponent, | ||
1032 | iState); | ||
1033 | } | ||
1034 | } | ||
1035 | |||
1036 | internal static uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes) | ||
1037 | { | ||
1038 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1039 | return NativeMethods.MsiSetFeatureAttributes(hInstall, szFeature, dwAttributes); | ||
1040 | else | ||
1041 | { | ||
1042 | return RemotableNativeMethods.MsiFunc_ISI( | ||
1043 | RemoteMsiFunctionId.MsiSetFeatureAttributes, | ||
1044 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1045 | szFeature, | ||
1046 | (int) dwAttributes); | ||
1047 | } | ||
1048 | } | ||
1049 | |||
1050 | internal static uint MsiSetFeatureState(int hInstall, string szFeature, int iState) | ||
1051 | { | ||
1052 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1053 | return NativeMethods.MsiSetFeatureState(hInstall, szFeature, iState); | ||
1054 | else | ||
1055 | { | ||
1056 | return RemotableNativeMethods.MsiFunc_ISI( | ||
1057 | RemoteMsiFunctionId.MsiSetFeatureState, | ||
1058 | RemotableNativeMethods.GetRemoteHandle(hInstall), szFeature, iState); | ||
1059 | } | ||
1060 | } | ||
1061 | |||
1062 | internal static uint MsiSetInstallLevel(int hInstall, int iInstallLevel) | ||
1063 | { | ||
1064 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1065 | return NativeMethods.MsiSetInstallLevel(hInstall, iInstallLevel); | ||
1066 | else | ||
1067 | { | ||
1068 | return RemotableNativeMethods.MsiFunc_III( | ||
1069 | RemoteMsiFunctionId.MsiSetInstallLevel, | ||
1070 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1071 | iInstallLevel, | ||
1072 | 0); | ||
1073 | } | ||
1074 | } | ||
1075 | |||
1076 | internal static uint MsiSetMode(int hInstall, uint iRunMode, bool fState) | ||
1077 | { | ||
1078 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1079 | return NativeMethods.MsiSetMode(hInstall, iRunMode, fState); | ||
1080 | else | ||
1081 | { | ||
1082 | return RemotableNativeMethods.MsiFunc_III( | ||
1083 | RemoteMsiFunctionId.MsiSetMode, | ||
1084 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1085 | (int) iRunMode, | ||
1086 | fState ? 1 : 0); | ||
1087 | } | ||
1088 | } | ||
1089 | |||
1090 | internal static uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath) | ||
1091 | { | ||
1092 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1093 | return NativeMethods.MsiSetTargetPath(hInstall, szFolder, szFolderPath); | ||
1094 | else | ||
1095 | { | ||
1096 | return RemotableNativeMethods.MsiFunc_ISS( | ||
1097 | RemoteMsiFunctionId.MsiSetTargetPath, | ||
1098 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1099 | szFolder, | ||
1100 | szFolderPath); | ||
1101 | } | ||
1102 | } | ||
1103 | |||
1104 | internal static uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf) | ||
1105 | { | ||
1106 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hSummaryInfo)) | ||
1107 | { | ||
1108 | return NativeMethods.MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, out uiDataType, out iValue, ref ftValue, szValueBuf, ref cchValueBuf); | ||
1109 | } | ||
1110 | else lock (RemotableNativeMethods.remotingDelegate) | ||
1111 | { | ||
1112 | ClearData(requestBuf); | ||
1113 | WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hSummaryInfo)); | ||
1114 | WriteInt(requestBuf, 1, (int) uiProperty); | ||
1115 | IntPtr resp; | ||
1116 | remotingDelegate(RemoteMsiFunctionId.MsiSummaryInfoGetProperty, requestBuf, out resp); | ||
1117 | unchecked | ||
1118 | { | ||
1119 | uint ret = (uint) ReadInt(resp, 0); | ||
1120 | if (ret == 0) | ||
1121 | { | ||
1122 | uiDataType = (uint) ReadInt(resp, 1); | ||
1123 | switch ((VarEnum) uiDataType) | ||
1124 | { | ||
1125 | case VarEnum.VT_I2: | ||
1126 | case VarEnum.VT_I4: | ||
1127 | iValue = ReadInt(resp, 2); | ||
1128 | break; | ||
1129 | |||
1130 | case VarEnum.VT_FILETIME: | ||
1131 | uint ftHigh = (uint) ReadInt(resp, 2); | ||
1132 | uint ftLow = (uint) ReadInt(resp, 3); | ||
1133 | ftValue = ((long) ftHigh) << 32 | ((long) ftLow); | ||
1134 | iValue = 0; | ||
1135 | break; | ||
1136 | |||
1137 | case VarEnum.VT_LPSTR: | ||
1138 | ReadString(resp, 2, szValueBuf, ref cchValueBuf); | ||
1139 | iValue = 0; | ||
1140 | break; | ||
1141 | |||
1142 | default: | ||
1143 | iValue = 0; | ||
1144 | break; | ||
1145 | } | ||
1146 | } | ||
1147 | else | ||
1148 | { | ||
1149 | uiDataType = 0; | ||
1150 | iValue = 0; | ||
1151 | } | ||
1152 | return ret; | ||
1153 | } | ||
1154 | } | ||
1155 | } | ||
1156 | |||
1157 | internal static uint MsiVerifyDiskSpace(int hInstall) | ||
1158 | { | ||
1159 | if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall)) | ||
1160 | return NativeMethods.MsiVerifyDiskSpace(hInstall); | ||
1161 | else | ||
1162 | { | ||
1163 | return RemotableNativeMethods.MsiFunc_III( | ||
1164 | RemoteMsiFunctionId.MsiVerifyDiskSpace, | ||
1165 | RemotableNativeMethods.GetRemoteHandle(hInstall), | ||
1166 | 0, | ||
1167 | 0); | ||
1168 | } | ||
1169 | } | ||
1170 | } | ||
1171 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs new file mode 100644 index 00000000..875e49a6 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs | |||
@@ -0,0 +1,946 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Runtime.InteropServices; | ||
10 | using System.Diagnostics.CodeAnalysis; | ||
11 | |||
12 | /// <summary> | ||
13 | /// The Session object controls the installation process. It opens the | ||
14 | /// install database, which contains the installation tables and data. | ||
15 | /// </summary> | ||
16 | /// <remarks><p> | ||
17 | /// This object is associated with a standard set of action functions, | ||
18 | /// each performing particular operations on data from one or more tables. Additional | ||
19 | /// custom actions may be added for particular product installations. The basic engine | ||
20 | /// function is a sequencer that fetches sequential records from a designated sequence | ||
21 | /// table, evaluates any specified condition expression, and executes the designated | ||
22 | /// action. Actions not recognized by the engine are deferred to the UI handler object | ||
23 | /// for processing, usually dialog box sequences. | ||
24 | /// </p><p> | ||
25 | /// Note that only one Session object can be opened by a single process. | ||
26 | /// </p></remarks> | ||
27 | public sealed class Session : InstallerHandle, IFormatProvider | ||
28 | { | ||
29 | private Database database; | ||
30 | private CustomActionData customActionData; | ||
31 | private bool sessionAccessValidated = false; | ||
32 | |||
33 | internal Session(IntPtr handle, bool ownsHandle) | ||
34 | : base(handle, ownsHandle) | ||
35 | { | ||
36 | } | ||
37 | |||
38 | /// <summary> | ||
39 | /// Gets the Database for the install session. | ||
40 | /// </summary> | ||
41 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
42 | /// <exception cref="InstallerException">the Database cannot be accessed</exception> | ||
43 | /// <remarks><p> | ||
44 | /// Normally there is no need to close this Database object. The same object can be | ||
45 | /// used throughout the lifetime of the Session, and it will be closed when the Session | ||
46 | /// is closed. | ||
47 | /// </p><p> | ||
48 | /// Win32 MSI API: | ||
49 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetactivedatabase.asp">MsiGetActiveDatabase</a> | ||
50 | /// </p></remarks> | ||
51 | [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] | ||
52 | public Database Database | ||
53 | { | ||
54 | get | ||
55 | { | ||
56 | if (this.database == null || this.database.IsClosed) | ||
57 | { | ||
58 | lock (this.Sync) | ||
59 | { | ||
60 | if (this.database == null || this.database.IsClosed) | ||
61 | { | ||
62 | this.ValidateSessionAccess(); | ||
63 | |||
64 | int hDb = RemotableNativeMethods.MsiGetActiveDatabase((int) this.Handle); | ||
65 | if (hDb == 0) | ||
66 | { | ||
67 | throw new InstallerException(); | ||
68 | } | ||
69 | this.database = new Database((IntPtr) hDb, true, "", DatabaseOpenMode.ReadOnly); | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | return this.database; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// Gets the numeric language ID used by the current install session. | ||
79 | /// </summary> | ||
80 | /// <remarks><p> | ||
81 | /// Win32 MSI API: | ||
82 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlanguage.asp">MsiGetLanguage</a> | ||
83 | /// </p></remarks> | ||
84 | public int Language | ||
85 | { | ||
86 | get | ||
87 | { | ||
88 | return (int) RemotableNativeMethods.MsiGetLanguage((int) this.Handle); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | /// <summary> | ||
93 | /// Gets or sets the string value of a named installer property, as maintained by the | ||
94 | /// Session object in the in-memory Property table, or, if it is prefixed with a percent | ||
95 | /// sign (%), the value of a system environment variable for the current process. | ||
96 | /// </summary> | ||
97 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
98 | /// <remarks><p> | ||
99 | /// Win32 MSI APIs: | ||
100 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproperty.asp">MsiGetProperty</a>, | ||
101 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetproperty.asp">MsiSetProperty</a> | ||
102 | /// </p></remarks> | ||
103 | public string this[string property] | ||
104 | { | ||
105 | get | ||
106 | { | ||
107 | if (String.IsNullOrEmpty(property)) | ||
108 | { | ||
109 | throw new ArgumentNullException("property"); | ||
110 | } | ||
111 | |||
112 | if (!this.sessionAccessValidated && | ||
113 | !Session.NonImmediatePropertyNames.Contains(property)) | ||
114 | { | ||
115 | this.ValidateSessionAccess(); | ||
116 | } | ||
117 | |||
118 | StringBuilder buf = new StringBuilder(); | ||
119 | uint bufSize = 0; | ||
120 | uint ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize); | ||
121 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
122 | { | ||
123 | buf.Capacity = (int) ++bufSize; | ||
124 | ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize); | ||
125 | } | ||
126 | |||
127 | if (ret != 0) | ||
128 | { | ||
129 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
130 | } | ||
131 | return buf.ToString(); | ||
132 | } | ||
133 | |||
134 | set | ||
135 | { | ||
136 | if (String.IsNullOrEmpty(property)) | ||
137 | { | ||
138 | throw new ArgumentNullException("property"); | ||
139 | } | ||
140 | |||
141 | this.ValidateSessionAccess(); | ||
142 | |||
143 | if (value == null) | ||
144 | { | ||
145 | value = String.Empty; | ||
146 | } | ||
147 | |||
148 | uint ret = RemotableNativeMethods.MsiSetProperty((int) this.Handle, property, value); | ||
149 | if (ret != 0) | ||
150 | { | ||
151 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | /// <summary> | ||
157 | /// Creates a new Session object from an integer session handle. | ||
158 | /// </summary> | ||
159 | /// <param name="handle">Integer session handle</param> | ||
160 | /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param> | ||
161 | /// <remarks><p> | ||
162 | /// This method is only provided for interop purposes. A Session object | ||
163 | /// should normally be obtained by calling <see cref="Installer.OpenPackage(Database,bool)"/> | ||
164 | /// or <see cref="Installer.OpenProduct"/>. | ||
165 | /// </p></remarks> | ||
166 | public static Session FromHandle(IntPtr handle, bool ownsHandle) | ||
167 | { | ||
168 | return new Session(handle, ownsHandle); | ||
169 | } | ||
170 | |||
171 | /// <summary> | ||
172 | /// Performs any enabled logging operations and defers execution to the UI handler | ||
173 | /// object associated with the engine. | ||
174 | /// </summary> | ||
175 | /// <param name="messageType">Type of message to be processed</param> | ||
176 | /// <param name="record">Contains message-specific fields</param> | ||
177 | /// <returns>A message-dependent return value</returns> | ||
178 | /// <exception cref="InvalidHandleException">the Session or Record handle is invalid</exception> | ||
179 | /// <exception cref="ArgumentOutOfRangeException">an invalid message kind is specified</exception> | ||
180 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
181 | /// <exception cref="InstallerException">the message-handler failed for an unknown reason</exception> | ||
182 | /// <remarks><p> | ||
183 | /// Logging may be selectively enabled for the various message types. | ||
184 | /// See the <see cref="Installer.EnableLog(InstallLogModes,string)"/> method. | ||
185 | /// </p><p> | ||
186 | /// If record field 0 contains a formatting string, it is used to format the data in | ||
187 | /// the other fields. Else if the message is an error, warning, or user message, an attempt | ||
188 | /// is made to find a message template in the Error table for the current database using the | ||
189 | /// error number found in field 1 of the record for message types and return values. | ||
190 | /// </p><p> | ||
191 | /// The <paramref name="messageType"/> parameter may also include message-box flags from | ||
192 | /// the following enumerations: System.Windows.Forms.MessageBoxButtons, | ||
193 | /// System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxIcon. These | ||
194 | /// flags can be combined with the InstallMessage with a bitwise OR. | ||
195 | /// </p><p> | ||
196 | /// Note, this method never returns Cancel or Error values. Instead, appropriate | ||
197 | /// exceptions are thrown in those cases. | ||
198 | /// </p><p> | ||
199 | /// Win32 MSI API: | ||
200 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a> | ||
201 | /// </p></remarks> | ||
202 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
203 | public MessageResult Message(InstallMessage messageType, Record record) | ||
204 | { | ||
205 | if (record == null) | ||
206 | { | ||
207 | throw new ArgumentNullException("record"); | ||
208 | } | ||
209 | |||
210 | int ret = RemotableNativeMethods.MsiProcessMessage((int) this.Handle, (uint) messageType, (int) record.Handle); | ||
211 | if (ret < 0) | ||
212 | { | ||
213 | throw new InstallerException(); | ||
214 | } | ||
215 | else if (ret == (int) MessageResult.Cancel) | ||
216 | { | ||
217 | throw new InstallCanceledException(); | ||
218 | } | ||
219 | return (MessageResult) ret; | ||
220 | } | ||
221 | |||
222 | /// <summary> | ||
223 | /// Writes a message to the log, if logging is enabled. | ||
224 | /// </summary> | ||
225 | /// <param name="msg">The line to be written to the log</param> | ||
226 | /// <remarks><p> | ||
227 | /// Win32 MSI API: | ||
228 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a> | ||
229 | /// </p></remarks> | ||
230 | public void Log(string msg) | ||
231 | { | ||
232 | if (msg == null) | ||
233 | { | ||
234 | throw new ArgumentNullException("msg"); | ||
235 | } | ||
236 | |||
237 | using (Record rec = new Record(0)) | ||
238 | { | ||
239 | rec.FormatString = msg; | ||
240 | this.Message(InstallMessage.Info, rec); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | /// <summary> | ||
245 | /// Writes a formatted message to the log, if logging is enabled. | ||
246 | /// </summary> | ||
247 | /// <param name="format">The line to be written to the log, containing 0 or more format specifiers</param> | ||
248 | /// <param name="args">An array containing 0 or more objects to be formatted</param> | ||
249 | /// <remarks><p> | ||
250 | /// Win32 MSI API: | ||
251 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a> | ||
252 | /// </p></remarks> | ||
253 | public void Log(string format, params object[] args) | ||
254 | { | ||
255 | this.Log(String.Format(CultureInfo.InvariantCulture, format, args)); | ||
256 | } | ||
257 | |||
258 | /// <summary> | ||
259 | /// Evaluates a logical expression containing symbols and values. | ||
260 | /// </summary> | ||
261 | /// <param name="condition">conditional expression</param> | ||
262 | /// <returns>The result of the condition evaluation</returns> | ||
263 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
264 | /// <exception cref="ArgumentNullException">the condition is null or empty</exception> | ||
265 | /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception> | ||
266 | /// <remarks><p> | ||
267 | /// Win32 MSI API: | ||
268 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a> | ||
269 | /// </p></remarks> | ||
270 | public bool EvaluateCondition(string condition) | ||
271 | { | ||
272 | if (String.IsNullOrEmpty(condition)) | ||
273 | { | ||
274 | throw new ArgumentNullException("condition"); | ||
275 | } | ||
276 | |||
277 | uint value = RemotableNativeMethods.MsiEvaluateCondition((int) this.Handle, condition); | ||
278 | if (value == 0) | ||
279 | { | ||
280 | return false; | ||
281 | } | ||
282 | else if (value == 1) | ||
283 | { | ||
284 | return true; | ||
285 | } | ||
286 | else | ||
287 | { | ||
288 | throw new InvalidOperationException(); | ||
289 | } | ||
290 | } | ||
291 | |||
292 | /// <summary> | ||
293 | /// Evaluates a logical expression containing symbols and values, specifying a default | ||
294 | /// value to be returned in case the condition is empty. | ||
295 | /// </summary> | ||
296 | /// <param name="condition">conditional expression</param> | ||
297 | /// <param name="defaultValue">value to return if the condition is empty</param> | ||
298 | /// <returns>The result of the condition evaluation</returns> | ||
299 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
300 | /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception> | ||
301 | /// <remarks><p> | ||
302 | /// Win32 MSI API: | ||
303 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a> | ||
304 | /// </p></remarks> | ||
305 | public bool EvaluateCondition(string condition, bool defaultValue) | ||
306 | { | ||
307 | if (condition == null) | ||
308 | { | ||
309 | throw new ArgumentNullException("condition"); | ||
310 | } | ||
311 | else if (condition.Length == 0) | ||
312 | { | ||
313 | return defaultValue; | ||
314 | } | ||
315 | else | ||
316 | { | ||
317 | this.ValidateSessionAccess(); | ||
318 | return this.EvaluateCondition(condition); | ||
319 | } | ||
320 | } | ||
321 | |||
322 | /// <summary> | ||
323 | /// Formats a string containing installer properties. | ||
324 | /// </summary> | ||
325 | /// <param name="format">A format string containing property tokens</param> | ||
326 | /// <returns>A formatted string containing property data</returns> | ||
327 | /// <exception cref="InvalidHandleException">the Record handle is invalid</exception> | ||
328 | /// <remarks><p> | ||
329 | /// Win32 MSI API: | ||
330 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
331 | /// </p></remarks> | ||
332 | [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames")] | ||
333 | public string Format(string format) | ||
334 | { | ||
335 | if (format == null) | ||
336 | { | ||
337 | throw new ArgumentNullException("format"); | ||
338 | } | ||
339 | |||
340 | using (Record formatRec = new Record(0)) | ||
341 | { | ||
342 | formatRec.FormatString = format; | ||
343 | return formatRec.ToString(this); | ||
344 | } | ||
345 | } | ||
346 | |||
347 | /// <summary> | ||
348 | /// Returns a formatted string from record data. | ||
349 | /// </summary> | ||
350 | /// <param name="record">Record object containing a template and data to be formatted. | ||
351 | /// The template string must be set in field 0 followed by any referenced data parameters.</param> | ||
352 | /// <returns>A formatted string containing the record data</returns> | ||
353 | /// <exception cref="InvalidHandleException">the Record handle is invalid</exception> | ||
354 | /// <remarks><p> | ||
355 | /// Win32 MSI API: | ||
356 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
357 | /// </p></remarks> | ||
358 | public string FormatRecord(Record record) | ||
359 | { | ||
360 | if (record == null) | ||
361 | { | ||
362 | throw new ArgumentNullException("record"); | ||
363 | } | ||
364 | |||
365 | return record.ToString(this); | ||
366 | } | ||
367 | |||
368 | /// <summary> | ||
369 | /// Returns a formatted string from record data using a specified format. | ||
370 | /// </summary> | ||
371 | /// <param name="record">Record object containing a template and data to be formatted</param> | ||
372 | /// <param name="format">Format string to be used instead of field 0 of the Record</param> | ||
373 | /// <returns>A formatted string containing the record data</returns> | ||
374 | /// <exception cref="InvalidHandleException">the Record handle is invalid</exception> | ||
375 | /// <remarks><p> | ||
376 | /// Win32 MSI API: | ||
377 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
378 | /// </p></remarks> | ||
379 | [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the Record's " + | ||
380 | "FormatString property separately before calling the FormatRecord() override that takes only the Record parameter.")] | ||
381 | public string FormatRecord(Record record, string format) | ||
382 | { | ||
383 | if (record == null) | ||
384 | { | ||
385 | throw new ArgumentNullException("record"); | ||
386 | } | ||
387 | |||
388 | return record.ToString(format, this); | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Retrieves product properties (not session properties) from the product database. | ||
393 | /// </summary> | ||
394 | /// <returns>Value of the property, or an empty string if the property is not set.</returns> | ||
395 | /// <remarks><p> | ||
396 | /// Note this is not the correct method for getting ordinary session properties. For that, | ||
397 | /// see the indexer on the Session class. | ||
398 | /// </p><p> | ||
399 | /// Win32 MSI API: | ||
400 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductproperty.asp">MsiGetProductProperty</a> | ||
401 | /// </p></remarks> | ||
402 | public string GetProductProperty(string property) | ||
403 | { | ||
404 | if (String.IsNullOrEmpty(property)) | ||
405 | { | ||
406 | throw new ArgumentNullException("property"); | ||
407 | } | ||
408 | |||
409 | this.ValidateSessionAccess(); | ||
410 | |||
411 | StringBuilder buf = new StringBuilder(); | ||
412 | uint bufSize = (uint) buf.Capacity; | ||
413 | uint ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize); | ||
414 | |||
415 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
416 | { | ||
417 | buf.Capacity = (int) ++bufSize; | ||
418 | ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize); | ||
419 | } | ||
420 | |||
421 | if (ret != 0) | ||
422 | { | ||
423 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
424 | } | ||
425 | return buf.ToString(); | ||
426 | } | ||
427 | |||
428 | /// <summary> | ||
429 | /// Gets an accessor for components in the current session. | ||
430 | /// </summary> | ||
431 | public ComponentInfoCollection Components | ||
432 | { | ||
433 | get | ||
434 | { | ||
435 | this.ValidateSessionAccess(); | ||
436 | return new ComponentInfoCollection(this); | ||
437 | } | ||
438 | } | ||
439 | |||
440 | /// <summary> | ||
441 | /// Gets an accessor for features in the current session. | ||
442 | /// </summary> | ||
443 | public FeatureInfoCollection Features | ||
444 | { | ||
445 | get | ||
446 | { | ||
447 | this.ValidateSessionAccess(); | ||
448 | return new FeatureInfoCollection(this); | ||
449 | } | ||
450 | } | ||
451 | |||
452 | /// <summary> | ||
453 | /// Checks to see if sufficient disk space is present for the current installation. | ||
454 | /// </summary> | ||
455 | /// <returns>True if there is sufficient disk space; false otherwise.</returns> | ||
456 | /// <remarks><p> | ||
457 | /// Win32 MSI API: | ||
458 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifydiskspace.asp">MsiVerifyDiskSpace</a> | ||
459 | /// </p></remarks> | ||
460 | public bool VerifyDiskSpace() | ||
461 | { | ||
462 | this.ValidateSessionAccess(); | ||
463 | |||
464 | uint ret = RemotableNativeMethods.MsiVerifyDiskSpace((int)this.Handle); | ||
465 | if (ret == (uint) NativeMethods.Error.DISK_FULL) | ||
466 | { | ||
467 | return false; | ||
468 | } | ||
469 | else if (ret != 0) | ||
470 | { | ||
471 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
472 | } | ||
473 | return true; | ||
474 | } | ||
475 | |||
476 | /// <summary> | ||
477 | /// Gets the total disk space per drive required for the installation. | ||
478 | /// </summary> | ||
479 | /// <returns>A list of InstallCost structures, specifying the cost for each drive</returns> | ||
480 | /// <remarks><p> | ||
481 | /// Win32 MSI API: | ||
482 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a> | ||
483 | /// </p></remarks> | ||
484 | [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] | ||
485 | public IList<InstallCost> GetTotalCost() | ||
486 | { | ||
487 | this.ValidateSessionAccess(); | ||
488 | |||
489 | IList<InstallCost> costs = new List<InstallCost>(); | ||
490 | StringBuilder driveBuf = new StringBuilder(20); | ||
491 | for (uint i = 0; true; i++) | ||
492 | { | ||
493 | int cost, tempCost; | ||
494 | uint driveBufSize = (uint) driveBuf.Capacity; | ||
495 | uint ret = RemotableNativeMethods.MsiEnumComponentCosts( | ||
496 | (int) this.Handle, | ||
497 | null, | ||
498 | i, | ||
499 | (int) InstallState.Default, | ||
500 | driveBuf, | ||
501 | ref driveBufSize, | ||
502 | out cost, | ||
503 | out tempCost); | ||
504 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; | ||
505 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
506 | { | ||
507 | driveBuf.Capacity = (int) ++driveBufSize; | ||
508 | ret = RemotableNativeMethods.MsiEnumComponentCosts( | ||
509 | (int) this.Handle, | ||
510 | null, | ||
511 | i, | ||
512 | (int) InstallState.Default, | ||
513 | driveBuf, | ||
514 | ref driveBufSize, | ||
515 | out cost, | ||
516 | out tempCost); | ||
517 | } | ||
518 | |||
519 | if (ret != 0) | ||
520 | { | ||
521 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
522 | } | ||
523 | costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L)); | ||
524 | } | ||
525 | return costs; | ||
526 | } | ||
527 | |||
528 | /// <summary> | ||
529 | /// Gets the designated mode flag for the current install session. | ||
530 | /// </summary> | ||
531 | /// <param name="mode">The type of mode to be checked.</param> | ||
532 | /// <returns>The value of the designated mode flag.</returns> | ||
533 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
534 | /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception> | ||
535 | /// <remarks><p> | ||
536 | /// Note that only the following run modes are available to read from | ||
537 | /// a deferred custom action:<list type="bullet"> | ||
538 | /// <item><description><see cref="InstallRunMode.Scheduled"/></description></item> | ||
539 | /// <item><description><see cref="InstallRunMode.Rollback"/></description></item> | ||
540 | /// <item><description><see cref="InstallRunMode.Commit"/></description></item> | ||
541 | /// </list> | ||
542 | /// </p><p> | ||
543 | /// Win32 MSI API: | ||
544 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetmode.asp">MsiGetMode</a> | ||
545 | /// </p></remarks> | ||
546 | public bool GetMode(InstallRunMode mode) | ||
547 | { | ||
548 | return RemotableNativeMethods.MsiGetMode((int) this.Handle, (uint) mode); | ||
549 | } | ||
550 | |||
551 | /// <summary> | ||
552 | /// Sets the designated mode flag for the current install session. | ||
553 | /// </summary> | ||
554 | /// <param name="mode">The type of mode to be set.</param> | ||
555 | /// <param name="value">The desired value of the mode.</param> | ||
556 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
557 | /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception> | ||
558 | /// <exception cref="InvalidOperationException">the mode cannot not be set</exception> | ||
559 | /// <remarks><p> | ||
560 | /// Win32 MSI API: | ||
561 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetmode.asp">MsiSetMode</a> | ||
562 | /// </p></remarks> | ||
563 | public void SetMode(InstallRunMode mode, bool value) | ||
564 | { | ||
565 | this.ValidateSessionAccess(); | ||
566 | |||
567 | uint ret = RemotableNativeMethods.MsiSetMode((int) this.Handle, (uint) mode, value); | ||
568 | if (ret != 0) | ||
569 | { | ||
570 | if (ret == (uint) NativeMethods.Error.ACCESS_DENIED) | ||
571 | { | ||
572 | throw new InvalidOperationException(); | ||
573 | } | ||
574 | else | ||
575 | { | ||
576 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
577 | } | ||
578 | } | ||
579 | } | ||
580 | |||
581 | /// <summary> | ||
582 | /// Gets the full path to the designated folder on the source media or server image. | ||
583 | /// </summary> | ||
584 | /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception> | ||
585 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
586 | /// <remarks><p> | ||
587 | /// Win32 MSI API: | ||
588 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsourcepath.asp">MsiGetSourcePath</a> | ||
589 | /// </p></remarks> | ||
590 | public string GetSourcePath(string directory) | ||
591 | { | ||
592 | if (String.IsNullOrEmpty(directory)) | ||
593 | { | ||
594 | throw new ArgumentNullException("directory"); | ||
595 | } | ||
596 | |||
597 | this.ValidateSessionAccess(); | ||
598 | |||
599 | StringBuilder buf = new StringBuilder(); | ||
600 | uint bufSize = 0; | ||
601 | uint ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize); | ||
602 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
603 | { | ||
604 | buf.Capacity = (int) ++bufSize; | ||
605 | ret = ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize); | ||
606 | } | ||
607 | |||
608 | if (ret != 0) | ||
609 | { | ||
610 | if (ret == (uint) NativeMethods.Error.DIRECTORY) | ||
611 | { | ||
612 | throw InstallerException.ExceptionFromReturnCode(ret, directory); | ||
613 | } | ||
614 | else | ||
615 | { | ||
616 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
617 | } | ||
618 | } | ||
619 | return buf.ToString(); | ||
620 | } | ||
621 | |||
622 | /// <summary> | ||
623 | /// Gets the full path to the designated folder on the installation target drive. | ||
624 | /// </summary> | ||
625 | /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception> | ||
626 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
627 | /// <remarks><p> | ||
628 | /// Win32 MSI API: | ||
629 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigettargetpath.asp">MsiGetTargetPath</a> | ||
630 | /// </p></remarks> | ||
631 | public string GetTargetPath(string directory) | ||
632 | { | ||
633 | if (String.IsNullOrEmpty(directory)) | ||
634 | { | ||
635 | throw new ArgumentNullException("directory"); | ||
636 | } | ||
637 | |||
638 | this.ValidateSessionAccess(); | ||
639 | |||
640 | StringBuilder buf = new StringBuilder(); | ||
641 | uint bufSize = 0; | ||
642 | uint ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize); | ||
643 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
644 | { | ||
645 | buf.Capacity = (int) ++bufSize; | ||
646 | ret = ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize); | ||
647 | } | ||
648 | |||
649 | if (ret != 0) | ||
650 | { | ||
651 | if (ret == (uint) NativeMethods.Error.DIRECTORY) | ||
652 | { | ||
653 | throw InstallerException.ExceptionFromReturnCode(ret, directory); | ||
654 | } | ||
655 | else | ||
656 | { | ||
657 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
658 | } | ||
659 | } | ||
660 | return buf.ToString(); | ||
661 | } | ||
662 | |||
663 | /// <summary> | ||
664 | /// Sets the full path to the designated folder on the installation target drive. | ||
665 | /// </summary> | ||
666 | /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception> | ||
667 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
668 | /// <remarks><p> | ||
669 | /// Setting the target path of a directory changes the path specification for the directory | ||
670 | /// in the in-memory Directory table. Also, the path specifications of all other path objects | ||
671 | /// in the table that are either subordinate or equivalent to the changed path are updated | ||
672 | /// to reflect the change. The properties for each affected path are also updated. | ||
673 | /// </p><p> | ||
674 | /// If an error occurs in this function, all updated paths and properties revert to | ||
675 | /// their previous values. Therefore, it is safe to treat errors returned by this function | ||
676 | /// as non-fatal. | ||
677 | /// </p><p> | ||
678 | /// Do not attempt to configure the target path if the components using those paths | ||
679 | /// are already installed for the current user or for a different user. Check the | ||
680 | /// ProductState property before setting the target path to determine if the product | ||
681 | /// containing this component is installed. | ||
682 | /// </p><p> | ||
683 | /// Win32 MSI API: | ||
684 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisettargetpath.asp">MsiSetTargetPath</a> | ||
685 | /// </p></remarks> | ||
686 | public void SetTargetPath(string directory, string value) | ||
687 | { | ||
688 | if (String.IsNullOrEmpty(directory)) | ||
689 | { | ||
690 | throw new ArgumentNullException("directory"); | ||
691 | } | ||
692 | |||
693 | if (value == null) | ||
694 | { | ||
695 | throw new ArgumentNullException("value"); | ||
696 | } | ||
697 | |||
698 | this.ValidateSessionAccess(); | ||
699 | |||
700 | uint ret = RemotableNativeMethods.MsiSetTargetPath((int) this.Handle, directory, value); | ||
701 | if (ret != 0) | ||
702 | { | ||
703 | if (ret == (uint) NativeMethods.Error.DIRECTORY) | ||
704 | { | ||
705 | throw InstallerException.ExceptionFromReturnCode(ret, directory); | ||
706 | } | ||
707 | else | ||
708 | { | ||
709 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
710 | } | ||
711 | } | ||
712 | } | ||
713 | |||
714 | /// <summary> | ||
715 | /// Sets the install level for the current installation to a specified value and | ||
716 | /// recalculates the Select and Installed states for all features in the Feature | ||
717 | /// table. Also sets the Action state of each component in the Component table based | ||
718 | /// on the new level. | ||
719 | /// </summary> | ||
720 | /// <param name="installLevel">New install level</param> | ||
721 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
722 | /// <remarks><p> | ||
723 | /// The SetInstallLevel method sets the following:<list type="bullet"> | ||
724 | /// <item><description>The installation level for the current installation to a specified value</description></item> | ||
725 | /// <item><description>The Select and Installed states for all features in the Feature table</description></item> | ||
726 | /// <item><description>The Action state of each component in the Component table, based on the new level</description></item> | ||
727 | /// </list> | ||
728 | /// If 0 or a negative number is passed in the ilnstallLevel parameter, | ||
729 | /// the current installation level does not change, but all features are still | ||
730 | /// updated based on the current installation level. | ||
731 | /// </p><p> | ||
732 | /// Win32 MSI API: | ||
733 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinstalllevel.asp">MsiSetInstallLevel</a> | ||
734 | /// </p></remarks> | ||
735 | public void SetInstallLevel(int installLevel) | ||
736 | { | ||
737 | this.ValidateSessionAccess(); | ||
738 | |||
739 | uint ret = RemotableNativeMethods.MsiSetInstallLevel((int) this.Handle, installLevel); | ||
740 | if (ret != 0) | ||
741 | { | ||
742 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
743 | } | ||
744 | } | ||
745 | |||
746 | /// <summary> | ||
747 | /// Executes a built-in action, custom action, or user-interface wizard action. | ||
748 | /// </summary> | ||
749 | /// <param name="action">Name of the action to execute. Case-sensitive.</param> | ||
750 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
751 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
752 | /// <remarks><p> | ||
753 | /// The DoAction method executes the action that corresponds to the name supplied. If the | ||
754 | /// name is not recognized by the installer as a built-in action or as a custom action in | ||
755 | /// the CustomAction table, the name is passed to the user-interface handler object, which | ||
756 | /// can invoke a function or a dialog box. If a null action name is supplied, the installer | ||
757 | /// uses the upper-case value of the ACTION property as the action to perform. If no property | ||
758 | /// value is defined, the default action is performed, defined as "INSTALL". | ||
759 | /// </p><p> | ||
760 | /// Actions that update the system, such as the InstallFiles and WriteRegistryValues | ||
761 | /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction | ||
762 | /// is called from a custom action that is scheduled in the InstallExecuteSequence table | ||
763 | /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the | ||
764 | /// system, such as AppSearch or CostInitialize, can be called. | ||
765 | /// </p><p> | ||
766 | /// Win32 MSI API: | ||
767 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a> | ||
768 | /// </p></remarks> | ||
769 | public void DoAction(string action) | ||
770 | { | ||
771 | this.DoAction(action, null); | ||
772 | } | ||
773 | |||
774 | /// <summary> | ||
775 | /// Executes a built-in action, custom action, or user-interface wizard action. | ||
776 | /// </summary> | ||
777 | /// <param name="action">Name of the action to execute. Case-sensitive.</param> | ||
778 | /// <param name="actionData">Optional data to be passed to a deferred custom action.</param> | ||
779 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
780 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
781 | /// <remarks><p> | ||
782 | /// The DoAction method executes the action that corresponds to the name supplied. If the | ||
783 | /// name is not recognized by the installer as a built-in action or as a custom action in | ||
784 | /// the CustomAction table, the name is passed to the user-interface handler object, which | ||
785 | /// can invoke a function or a dialog box. If a null action name is supplied, the installer | ||
786 | /// uses the upper-case value of the ACTION property as the action to perform. If no property | ||
787 | /// value is defined, the default action is performed, defined as "INSTALL". | ||
788 | /// </p><p> | ||
789 | /// Actions that update the system, such as the InstallFiles and WriteRegistryValues | ||
790 | /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction | ||
791 | /// is called from a custom action that is scheduled in the InstallExecuteSequence table | ||
792 | /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the | ||
793 | /// system, such as AppSearch or CostInitialize, can be called. | ||
794 | /// </p><p> | ||
795 | /// If the called action is a deferred, rollback, or commit custom action, then the supplied | ||
796 | /// <paramref name="actionData"/> will be available via the <see cref="CustomActionData"/> | ||
797 | /// property of that custom action's session. | ||
798 | /// </p><p> | ||
799 | /// Win32 MSI API: | ||
800 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a> | ||
801 | /// </p></remarks> | ||
802 | public void DoAction(string action, CustomActionData actionData) | ||
803 | { | ||
804 | if (String.IsNullOrEmpty(action)) | ||
805 | { | ||
806 | throw new ArgumentNullException("action"); | ||
807 | } | ||
808 | |||
809 | this.ValidateSessionAccess(); | ||
810 | |||
811 | if (actionData != null) | ||
812 | { | ||
813 | this[action] = actionData.ToString(); | ||
814 | } | ||
815 | |||
816 | uint ret = RemotableNativeMethods.MsiDoAction((int) this.Handle, action); | ||
817 | if (ret != 0) | ||
818 | { | ||
819 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
820 | } | ||
821 | } | ||
822 | |||
823 | /// <summary> | ||
824 | /// Executes an action sequence described in the specified table. | ||
825 | /// </summary> | ||
826 | /// <param name="sequenceTable">Name of the table containing the action sequence.</param> | ||
827 | /// <exception cref="InvalidHandleException">the Session handle is invalid</exception> | ||
828 | /// <exception cref="InstallCanceledException">the user exited the installation</exception> | ||
829 | /// <remarks><p> | ||
830 | /// This method queries the specified table, ordering the actions by the numbers in the Sequence column. | ||
831 | /// For each row retrieved, an action is executed, provided that any supplied condition expression does | ||
832 | /// not evaluate to FALSE. | ||
833 | /// </p><p> | ||
834 | /// An action sequence containing any actions that update the system, such as the InstallFiles and | ||
835 | /// WriteRegistryValues actions, cannot be run by calling DoActionSequence. The exception to this rule is if | ||
836 | /// DoActionSequence is called from a custom action that is scheduled in the InstallExecuteSequence table | ||
837 | /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the system, such | ||
838 | /// as AppSearch or CostInitialize, can be called. | ||
839 | /// </p><p> | ||
840 | /// Win32 MSI API: | ||
841 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisequence.asp">MsiSequence</a> | ||
842 | /// </p></remarks> | ||
843 | public void DoActionSequence(string sequenceTable) | ||
844 | { | ||
845 | if (String.IsNullOrEmpty(sequenceTable)) | ||
846 | { | ||
847 | throw new ArgumentNullException("sequenceTable"); | ||
848 | } | ||
849 | |||
850 | this.ValidateSessionAccess(); | ||
851 | |||
852 | uint ret = RemotableNativeMethods.MsiSequence((int) this.Handle, sequenceTable, 0); | ||
853 | if (ret != 0) | ||
854 | { | ||
855 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
856 | } | ||
857 | } | ||
858 | |||
859 | /// <summary> | ||
860 | /// Gets custom action data for the session that was supplied by the caller. | ||
861 | /// </summary> | ||
862 | /// <seealso cref="DoAction(string,CustomActionData)"/> | ||
863 | public CustomActionData CustomActionData | ||
864 | { | ||
865 | get | ||
866 | { | ||
867 | if (this.customActionData == null) | ||
868 | { | ||
869 | this.customActionData = new CustomActionData(this[CustomActionData.PropertyName]); | ||
870 | } | ||
871 | |||
872 | return this.customActionData; | ||
873 | } | ||
874 | } | ||
875 | |||
876 | /// <summary> | ||
877 | /// Implements formatting for <see cref="Record" /> data. | ||
878 | /// </summary> | ||
879 | /// <param name="formatType">Type of format object to get.</param> | ||
880 | /// <returns>The the current instance, if <paramref name="formatType"/> is the same type | ||
881 | /// as the current instance; otherwise, null.</returns> | ||
882 | object IFormatProvider.GetFormat(Type formatType) | ||
883 | { | ||
884 | return formatType == typeof(Session) ? this : null; | ||
885 | } | ||
886 | |||
887 | /// <summary> | ||
888 | /// Closes the session handle. Also closes the active database handle, if it is open. | ||
889 | /// After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>. | ||
890 | /// </summary> | ||
891 | /// <param name="disposing">If true, the method has been called directly | ||
892 | /// or indirectly by a user's code, so managed and unmanaged resources will | ||
893 | /// be disposed. If false, only unmanaged resources will be disposed.</param> | ||
894 | protected override void Dispose(bool disposing) | ||
895 | { | ||
896 | try | ||
897 | { | ||
898 | if (disposing) | ||
899 | { | ||
900 | if (this.database != null) | ||
901 | { | ||
902 | this.database.Dispose(); | ||
903 | this.database = null; | ||
904 | } | ||
905 | } | ||
906 | } | ||
907 | finally | ||
908 | { | ||
909 | base.Dispose(disposing); | ||
910 | } | ||
911 | } | ||
912 | |||
913 | /// <summary> | ||
914 | /// Gets the (short) list of properties that are available from non-immediate custom actions. | ||
915 | /// </summary> | ||
916 | private static IList<string> NonImmediatePropertyNames | ||
917 | { | ||
918 | get | ||
919 | { | ||
920 | return new string[] { | ||
921 | CustomActionData.PropertyName, | ||
922 | "ProductCode", | ||
923 | "UserSID" | ||
924 | }; | ||
925 | } | ||
926 | } | ||
927 | |||
928 | /// <summary> | ||
929 | /// Throws an exception if the custom action is not able to access immedate session details. | ||
930 | /// </summary> | ||
931 | private void ValidateSessionAccess() | ||
932 | { | ||
933 | if (!this.sessionAccessValidated) | ||
934 | { | ||
935 | if (this.GetMode(InstallRunMode.Scheduled) || | ||
936 | this.GetMode(InstallRunMode.Rollback) || | ||
937 | this.GetMode(InstallRunMode.Commit)) | ||
938 | { | ||
939 | throw new InstallerException("Cannot access session details from a non-immediate custom action"); | ||
940 | } | ||
941 | |||
942 | this.sessionAccessValidated = true; | ||
943 | } | ||
944 | } | ||
945 | } | ||
946 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs new file mode 100644 index 00000000..4c043bf2 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs | |||
@@ -0,0 +1,104 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | /// <summary> | ||
6 | /// Holds information about the target of a shortcut file. | ||
7 | /// </summary> | ||
8 | public struct ShortcutTarget | ||
9 | { | ||
10 | private string productCode; | ||
11 | private string feature; | ||
12 | private string componentCode; | ||
13 | |||
14 | internal ShortcutTarget(string productCode, string feature, string componentCode) | ||
15 | { | ||
16 | this.productCode = productCode; | ||
17 | this.feature = feature; | ||
18 | this.componentCode = componentCode; | ||
19 | } | ||
20 | |||
21 | /// <summary> | ||
22 | /// Gets the target product code of the shortcut, or null if not available. | ||
23 | /// </summary> | ||
24 | public string ProductCode | ||
25 | { | ||
26 | get | ||
27 | { | ||
28 | return this.productCode; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Gets the name of the target feature of the shortcut, or null if not available. | ||
34 | /// </summary> | ||
35 | public string Feature | ||
36 | { | ||
37 | get | ||
38 | { | ||
39 | return this.feature; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | /// <summary> | ||
44 | /// Gets the target component code of the shortcut, or null if not available. | ||
45 | /// </summary> | ||
46 | public string ComponentCode | ||
47 | { | ||
48 | get | ||
49 | { | ||
50 | return this.componentCode; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | /// <summary> | ||
55 | /// Tests whether two shortcut targets have the same product code, feature, and/or component code. | ||
56 | /// </summary> | ||
57 | /// <param name="st1">The first shortcut target to compare.</param> | ||
58 | /// <param name="st2">The second shortcut target to compare.</param> | ||
59 | /// <returns>True if all parts of the targets are the same, else false.</returns> | ||
60 | public static bool operator ==(ShortcutTarget st1, ShortcutTarget st2) | ||
61 | { | ||
62 | return st1.Equals(st2); | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Tests whether two shortcut targets have the same product code, feature, and/or component code. | ||
67 | /// </summary> | ||
68 | /// <param name="st1">The first shortcut target to compare.</param> | ||
69 | /// <param name="st2">The second shortcut target to compare.</param> | ||
70 | /// <returns>True if any parts of the targets are different, else false.</returns> | ||
71 | public static bool operator !=(ShortcutTarget st1, ShortcutTarget st2) | ||
72 | { | ||
73 | return !st1.Equals(st2); | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Tests whether two shortcut targets have the same product code, feature, and/or component code. | ||
78 | /// </summary> | ||
79 | /// <param name="obj">The shortcut target to compare to the current object.</param> | ||
80 | /// <returns>True if <paramref name="obj"/> is a shortcut target and all parts of the targets are the same, else false.</returns> | ||
81 | public override bool Equals(object obj) | ||
82 | { | ||
83 | if (obj == null || obj.GetType() != typeof(ShortcutTarget)) | ||
84 | { | ||
85 | return false; | ||
86 | } | ||
87 | ShortcutTarget st = (ShortcutTarget) obj; | ||
88 | return this.productCode == st.productCode | ||
89 | && this.feature == st.feature | ||
90 | && this.componentCode == st.componentCode; | ||
91 | } | ||
92 | |||
93 | /// <summary> | ||
94 | /// Generates a hash code using all parts of the shortcut target. | ||
95 | /// </summary> | ||
96 | /// <returns>An integer suitable for hashing the shortcut target.</returns> | ||
97 | public override int GetHashCode() | ||
98 | { | ||
99 | return (this.productCode != null ? this.productCode.GetHashCode() : 0) | ||
100 | ^ (this.feature != null ? this.feature.GetHashCode() : 0) | ||
101 | ^ (this.componentCode != null ? this.componentCode.GetHashCode() : 0); | ||
102 | } | ||
103 | } | ||
104 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs new file mode 100644 index 00000000..16ec22e8 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs | |||
@@ -0,0 +1,525 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// A list of sources for an installed product or patch. | ||
13 | /// </summary> | ||
14 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] | ||
15 | public class SourceList : ICollection<string> | ||
16 | { | ||
17 | private Installation installation; | ||
18 | private SourceMediaList mediaList; | ||
19 | |||
20 | internal SourceList(Installation installation) | ||
21 | { | ||
22 | this.installation = installation; | ||
23 | } | ||
24 | |||
25 | /// <summary> | ||
26 | /// Gets the list of disks registered for the media source of | ||
27 | /// the patch or product installation. | ||
28 | /// </summary> | ||
29 | public SourceMediaList MediaList | ||
30 | { | ||
31 | get | ||
32 | { | ||
33 | if (this.mediaList == null) | ||
34 | { | ||
35 | this.mediaList = new SourceMediaList(this.installation); | ||
36 | } | ||
37 | return this.mediaList; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets the number of network and URL sources in the list. | ||
43 | /// </summary> | ||
44 | public int Count | ||
45 | { | ||
46 | get | ||
47 | { | ||
48 | int count = 0; | ||
49 | IEnumerator<string> e = this.GetEnumerator(); | ||
50 | while (e.MoveNext()) | ||
51 | { | ||
52 | count++; | ||
53 | } | ||
54 | return count; | ||
55 | } | ||
56 | } | ||
57 | |||
58 | /// <summary> | ||
59 | /// Gets a boolean value indicating whether the list is read-only. | ||
60 | /// A SourceList is never read-only. | ||
61 | /// </summary> | ||
62 | /// <value>read-only status of the list</value> | ||
63 | public bool IsReadOnly | ||
64 | { | ||
65 | get { return false; } | ||
66 | } | ||
67 | |||
68 | /// <summary> | ||
69 | /// Adds a network or URL source to the source list of the installed product. | ||
70 | /// </summary> | ||
71 | /// <param name="item">Path to the source to be added. This parameter is | ||
72 | /// expected to contain only the path without the filename.</param> | ||
73 | /// <remarks><p> | ||
74 | /// If this method is called with a new source, the installer adds the source | ||
75 | /// to the end of the source list. | ||
76 | /// </p><p> | ||
77 | /// If this method is called with a source already existing in the source | ||
78 | /// list, it has no effect. | ||
79 | /// </p><p> | ||
80 | /// Win32 MSI APIs: | ||
81 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsource.asp">MsiSourceListAddSource</a>, | ||
82 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a> | ||
83 | /// </p></remarks> | ||
84 | /// <seealso cref="Insert"/> | ||
85 | public void Add(string item) | ||
86 | { | ||
87 | if (!this.Contains(item)) | ||
88 | { | ||
89 | this.Insert(item, 0); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | /// <summary> | ||
94 | /// Adds or reorders a network or URL source for the product or patch. | ||
95 | /// </summary> | ||
96 | /// <param name="item">Path to the source to be added. This parameter is | ||
97 | /// expected to contain only the path without the filename.</param> | ||
98 | /// <param name="index">Specifies the priority order in which the source | ||
99 | /// will be inserted</param> | ||
100 | /// <remarks><p> | ||
101 | /// If this method is called with a new source and <paramref name="index"/> | ||
102 | /// is set to 0, the installer adds the source to the end of the source list. | ||
103 | /// </p><p> | ||
104 | /// If this method is called with a source already existing in the source | ||
105 | /// list and <paramref name="index"/> is set to 0, the installer retains the | ||
106 | /// source's existing index. | ||
107 | /// </p><p> | ||
108 | /// If the method is called with an existing source in the source list | ||
109 | /// and <paramref name="index"/> is set to a non-zero value, the source is | ||
110 | /// removed from its current location in the list and inserted at the position | ||
111 | /// specified by Index, before any source that already exists at that position. | ||
112 | /// </p><p> | ||
113 | /// If the method is called with a new source and Index is set to a | ||
114 | /// non-zero value, the source is inserted at the position specified by | ||
115 | /// <paramref name="index"/>, before any source that already exists at | ||
116 | /// that position. The index value for all sources in the list after the | ||
117 | /// index specified by Index are updated to ensure unique index values and | ||
118 | /// the pre-existing order is guaranteed to remain unchanged. | ||
119 | /// </p><p> | ||
120 | /// If <paramref name="index"/> is greater than the number of sources | ||
121 | /// in the list, the source is placed at the end of the list with an index | ||
122 | /// value one larger than any existing source. | ||
123 | /// </p><p> | ||
124 | /// Win32 MSI API: | ||
125 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a> | ||
126 | /// </p></remarks> | ||
127 | public void Insert(string item, int index) | ||
128 | { | ||
129 | if (item == null) | ||
130 | { | ||
131 | throw new ArgumentNullException("item"); | ||
132 | } | ||
133 | |||
134 | NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network; | ||
135 | |||
136 | uint ret = NativeMethods.MsiSourceListAddSourceEx( | ||
137 | this.installation.InstallationCode, | ||
138 | this.installation.UserSid, | ||
139 | this.installation.Context, | ||
140 | (uint) type | (uint) this.installation.InstallationType, | ||
141 | item, | ||
142 | (uint) index); | ||
143 | if (ret != 0) | ||
144 | { | ||
145 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
146 | } | ||
147 | } | ||
148 | |||
149 | /// <summary> | ||
150 | /// Clears sources of all types: network, url, and media. | ||
151 | /// </summary> | ||
152 | /// <remarks><p> | ||
153 | /// Win32 MSI API: | ||
154 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearall.asp">MsiSourceListClearAll</a> | ||
155 | /// </p></remarks> | ||
156 | public void Clear() | ||
157 | { | ||
158 | this.ClearSourceType(NativeMethods.SourceType.Url); | ||
159 | this.ClearSourceType(NativeMethods.SourceType.Network); | ||
160 | this.MediaList.Clear(); | ||
161 | } | ||
162 | |||
163 | /// <summary> | ||
164 | /// Removes all network sources from the list. URL sources are not affected. | ||
165 | /// </summary> | ||
166 | /// <remarks><p> | ||
167 | /// Win32 MSI API: | ||
168 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a> | ||
169 | /// </p></remarks> | ||
170 | public void ClearNetworkSources() | ||
171 | { | ||
172 | this.ClearSourceType(NativeMethods.SourceType.Network); | ||
173 | } | ||
174 | |||
175 | /// <summary> | ||
176 | /// Removes all URL sources from the list. Network sources are not affected. | ||
177 | /// </summary> | ||
178 | /// <remarks><p> | ||
179 | /// Win32 MSI API: | ||
180 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a> | ||
181 | /// </p></remarks> | ||
182 | public void ClearUrlSources() | ||
183 | { | ||
184 | this.ClearSourceType(NativeMethods.SourceType.Url); | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Checks if the specified source exists in the list. | ||
189 | /// </summary> | ||
190 | /// <param name="item">case-insensitive source to look for</param> | ||
191 | /// <returns>true if the source exists in the list, false otherwise</returns> | ||
192 | public bool Contains(string item) | ||
193 | { | ||
194 | if (String.IsNullOrEmpty(item)) | ||
195 | { | ||
196 | throw new ArgumentNullException("item"); | ||
197 | } | ||
198 | |||
199 | foreach (string s in this) | ||
200 | { | ||
201 | if (s.Equals(item, StringComparison.OrdinalIgnoreCase)) | ||
202 | { | ||
203 | return true; | ||
204 | } | ||
205 | } | ||
206 | return false; | ||
207 | } | ||
208 | |||
209 | /// <summary> | ||
210 | /// Copies the network and URL sources from this list into an array. | ||
211 | /// </summary> | ||
212 | /// <param name="array">destination array to be filed</param> | ||
213 | /// <param name="arrayIndex">offset into the destination array where copying begins</param> | ||
214 | public void CopyTo(string[] array, int arrayIndex) | ||
215 | { | ||
216 | foreach (string source in this) | ||
217 | { | ||
218 | array[arrayIndex++] = source; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | /// <summary> | ||
223 | /// Removes a network or URL source. | ||
224 | /// </summary> | ||
225 | /// <remarks><p> | ||
226 | /// Win32 MSI API: | ||
227 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearsource.asp">MsiSourceListClearSource</a> | ||
228 | /// </p></remarks> | ||
229 | public bool Remove(string item) | ||
230 | { | ||
231 | if (String.IsNullOrEmpty(item)) | ||
232 | { | ||
233 | throw new ArgumentNullException("item"); | ||
234 | } | ||
235 | |||
236 | NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network; | ||
237 | |||
238 | uint ret = NativeMethods.MsiSourceListClearSource( | ||
239 | this.installation.InstallationCode, | ||
240 | this.installation.UserSid, | ||
241 | this.installation.Context, | ||
242 | (uint) type | (uint) this.installation.InstallationType, | ||
243 | item); | ||
244 | if (ret != 0) | ||
245 | { | ||
246 | // TODO: Figure out when to return false. | ||
247 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
248 | } | ||
249 | return true; | ||
250 | } | ||
251 | |||
252 | /// <summary> | ||
253 | /// Enumerates the network and URL sources in the source list of the patch or product installation. | ||
254 | /// </summary> | ||
255 | /// <remarks><p> | ||
256 | /// Win32 MSI API: | ||
257 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenumsources.asp">MsiSourceListEnumSources</a> | ||
258 | /// </p></remarks> | ||
259 | public IEnumerator<string> GetEnumerator() | ||
260 | { | ||
261 | StringBuilder sourceBuf = new StringBuilder(256); | ||
262 | uint sourceBufSize = (uint) sourceBuf.Capacity; | ||
263 | for (uint i = 0; true; i++) | ||
264 | { | ||
265 | uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Network); | ||
266 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
267 | { | ||
268 | break; | ||
269 | } | ||
270 | else if (ret != 0) | ||
271 | { | ||
272 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
273 | } | ||
274 | else | ||
275 | { | ||
276 | yield return sourceBuf.ToString(); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | for (uint i = 0; true; i++) | ||
281 | { | ||
282 | uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Url); | ||
283 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
284 | { | ||
285 | break; | ||
286 | } | ||
287 | else if (ret != 0) | ||
288 | { | ||
289 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
290 | } | ||
291 | else | ||
292 | { | ||
293 | yield return sourceBuf.ToString(); | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | |||
298 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
299 | { | ||
300 | return this.GetEnumerator(); | ||
301 | } | ||
302 | |||
303 | /// <summary> | ||
304 | /// Forces the installer to search the source list for a valid | ||
305 | /// source the next time a source is required. For example, when the | ||
306 | /// installer performs an installation or reinstallation, or when it | ||
307 | /// requires the path for a component that is set to run from source. | ||
308 | /// </summary> | ||
309 | /// <remarks><p> | ||
310 | /// Win32 MSI APIs: | ||
311 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolution.asp">MsiSourceListForceResolution</a>, | ||
312 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolutionex.asp">MsiSourceListForceResolutionEx</a> | ||
313 | /// </p></remarks> | ||
314 | public void ForceResolution() | ||
315 | { | ||
316 | uint ret = NativeMethods.MsiSourceListForceResolutionEx( | ||
317 | this.installation.InstallationCode, | ||
318 | this.installation.UserSid, | ||
319 | this.installation.Context, | ||
320 | (uint) this.installation.InstallationType); | ||
321 | if (ret != 0) | ||
322 | { | ||
323 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
324 | } | ||
325 | } | ||
326 | |||
327 | /// <summary> | ||
328 | /// Gets or sets the path relative to the root of the installation media. | ||
329 | /// </summary> | ||
330 | public string MediaPackagePath | ||
331 | { | ||
332 | get | ||
333 | { | ||
334 | return this["MediaPackagePath"]; | ||
335 | } | ||
336 | set | ||
337 | { | ||
338 | this["MediaPackagePath"] = value; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | /// <summary> | ||
343 | /// Gets or sets the prompt template that is used when prompting the user | ||
344 | /// for installation media. | ||
345 | /// </summary> | ||
346 | public string DiskPrompt | ||
347 | { | ||
348 | get | ||
349 | { | ||
350 | return this["DiskPrompt"]; | ||
351 | } | ||
352 | set | ||
353 | { | ||
354 | this["DiskPrompt"] = value; | ||
355 | } | ||
356 | } | ||
357 | |||
358 | /// <summary> | ||
359 | /// Gets or sets the most recently used source location for the product. | ||
360 | /// </summary> | ||
361 | public string LastUsedSource | ||
362 | { | ||
363 | get | ||
364 | { | ||
365 | return this["LastUsedSource"]; | ||
366 | } | ||
367 | set | ||
368 | { | ||
369 | this["LastUsedSource"] = value; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// Gets or sets the name of the Windows Installer package or patch package | ||
375 | /// on the source. | ||
376 | /// </summary> | ||
377 | public string PackageName | ||
378 | { | ||
379 | get | ||
380 | { | ||
381 | return this["PackageName"]; | ||
382 | } | ||
383 | set | ||
384 | { | ||
385 | this["PackageName"] = value; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | /// <summary> | ||
390 | /// Gets the type of the last-used source. | ||
391 | /// </summary> | ||
392 | /// <remarks><p> | ||
393 | /// <ul> | ||
394 | /// <li>"n" = network location</li> | ||
395 | /// <li>"u" = URL location</li> | ||
396 | /// <li>"m" = media location</li> | ||
397 | /// <li>(empty string) = no last used source</li> | ||
398 | /// </ul> | ||
399 | /// </p></remarks> | ||
400 | public string LastUsedType | ||
401 | { | ||
402 | get | ||
403 | { | ||
404 | return this["LastUsedType"]; | ||
405 | } | ||
406 | } | ||
407 | |||
408 | /// <summary> | ||
409 | /// Gets or sets source list information properties of a product or patch installation. | ||
410 | /// </summary> | ||
411 | /// <param name="property">The source list information property name.</param> | ||
412 | /// <exception cref="ArgumentOutOfRangeException">An unknown product, patch, or property was requested</exception> | ||
413 | /// <remarks><p> | ||
414 | /// Win32 MSI API: | ||
415 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistgetinfo.asp">MsiSourceListGetInfo</a> | ||
416 | /// </p></remarks> | ||
417 | public string this[string property] | ||
418 | { | ||
419 | get | ||
420 | { | ||
421 | StringBuilder buf = new StringBuilder(""); | ||
422 | uint bufSize = 0; | ||
423 | uint ret = NativeMethods.MsiSourceListGetInfo( | ||
424 | this.installation.InstallationCode, | ||
425 | this.installation.UserSid, | ||
426 | this.installation.Context, | ||
427 | (uint) this.installation.InstallationType, | ||
428 | property, | ||
429 | buf, | ||
430 | ref bufSize); | ||
431 | if (ret != 0) | ||
432 | { | ||
433 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
434 | { | ||
435 | buf.Capacity = (int) ++bufSize; | ||
436 | ret = NativeMethods.MsiSourceListGetInfo( | ||
437 | this.installation.InstallationCode, | ||
438 | this.installation.UserSid, | ||
439 | this.installation.Context, | ||
440 | (uint) this.installation.InstallationType, | ||
441 | property, | ||
442 | buf, | ||
443 | ref bufSize); | ||
444 | } | ||
445 | |||
446 | if (ret != 0) | ||
447 | { | ||
448 | if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT || | ||
449 | ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY) | ||
450 | { | ||
451 | throw new ArgumentOutOfRangeException("property"); | ||
452 | } | ||
453 | else | ||
454 | { | ||
455 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
456 | } | ||
457 | } | ||
458 | } | ||
459 | return buf.ToString(); | ||
460 | } | ||
461 | set | ||
462 | { | ||
463 | uint ret = NativeMethods.MsiSourceListSetInfo( | ||
464 | this.installation.InstallationCode, | ||
465 | this.installation.UserSid, | ||
466 | this.installation.Context, | ||
467 | (uint) this.installation.InstallationType, | ||
468 | property, | ||
469 | value); | ||
470 | if (ret != 0) | ||
471 | { | ||
472 | if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT || | ||
473 | ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY) | ||
474 | { | ||
475 | throw new ArgumentOutOfRangeException("property"); | ||
476 | } | ||
477 | else | ||
478 | { | ||
479 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
480 | } | ||
481 | } | ||
482 | } | ||
483 | } | ||
484 | |||
485 | private void ClearSourceType(NativeMethods.SourceType type) | ||
486 | { | ||
487 | uint ret = NativeMethods.MsiSourceListClearAllEx( | ||
488 | this.installation.InstallationCode, | ||
489 | this.installation.UserSid, | ||
490 | this.installation.Context, | ||
491 | (uint) type | (uint) this.installation.InstallationType); | ||
492 | if (ret != 0) | ||
493 | { | ||
494 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
495 | } | ||
496 | } | ||
497 | |||
498 | private uint EnumSources(StringBuilder sourceBuf, uint i, NativeMethods.SourceType sourceType) | ||
499 | { | ||
500 | int enumType = (this.installation.InstallationType | (int) sourceType); | ||
501 | uint sourceBufSize = (uint) sourceBuf.Capacity; | ||
502 | uint ret = NativeMethods.MsiSourceListEnumSources( | ||
503 | this.installation.InstallationCode, | ||
504 | this.installation.UserSid, | ||
505 | this.installation.Context, | ||
506 | (uint) enumType, | ||
507 | i, | ||
508 | sourceBuf, | ||
509 | ref sourceBufSize); | ||
510 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
511 | { | ||
512 | sourceBuf.Capacity = (int) ++sourceBufSize; | ||
513 | ret = NativeMethods.MsiSourceListEnumSources( | ||
514 | this.installation.InstallationCode, | ||
515 | this.installation.UserSid, | ||
516 | this.installation.Context, | ||
517 | (uint) enumType, | ||
518 | i, | ||
519 | sourceBuf, | ||
520 | ref sourceBufSize); | ||
521 | } | ||
522 | return ret; | ||
523 | } | ||
524 | } | ||
525 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs new file mode 100644 index 00000000..cf7b7ec5 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs | |||
@@ -0,0 +1,229 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Text; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// A list of source media for an installed product or patch. | ||
13 | /// </summary> | ||
14 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] | ||
15 | public class SourceMediaList : ICollection<MediaDisk> | ||
16 | { | ||
17 | private Installation installation; | ||
18 | |||
19 | internal SourceMediaList(Installation installation) | ||
20 | { | ||
21 | this.installation = installation; | ||
22 | } | ||
23 | |||
24 | /// <summary> | ||
25 | /// Gets the number of source media in the list. | ||
26 | /// </summary> | ||
27 | public int Count | ||
28 | { | ||
29 | get | ||
30 | { | ||
31 | int count = 0; | ||
32 | IEnumerator<MediaDisk> e = this.GetEnumerator(); | ||
33 | while (e.MoveNext()) | ||
34 | { | ||
35 | count++; | ||
36 | } | ||
37 | return count; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets a boolean value indicating whether the list is read-only. | ||
43 | /// A SourceMediaList is never read-only. | ||
44 | /// </summary> | ||
45 | /// <value>read-only status of the list</value> | ||
46 | public bool IsReadOnly | ||
47 | { | ||
48 | get | ||
49 | { | ||
50 | return false; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | /// <summary> | ||
55 | /// Adds or updates a disk of the media source for the product or patch. | ||
56 | /// </summary> | ||
57 | /// <remarks><p> | ||
58 | /// Win32 MSI API: | ||
59 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddmediadisk.asp">MsiSourceListAddMediaDisk</a> | ||
60 | /// </p></remarks> | ||
61 | public void Add(MediaDisk item) | ||
62 | { | ||
63 | uint ret = NativeMethods.MsiSourceListAddMediaDisk( | ||
64 | this.installation.InstallationCode, | ||
65 | this.installation.UserSid, | ||
66 | this.installation.Context, | ||
67 | (uint) this.installation.InstallationType, | ||
68 | (uint) item.DiskId, | ||
69 | item.VolumeLabel, | ||
70 | item.DiskPrompt); | ||
71 | |||
72 | if (ret != 0) | ||
73 | { | ||
74 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | /// <summary> | ||
79 | /// Removes all source media from the list. | ||
80 | /// </summary> | ||
81 | /// <remarks><p> | ||
82 | /// Win32 MSI API: | ||
83 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a> | ||
84 | /// </p></remarks> | ||
85 | public void Clear() | ||
86 | { | ||
87 | uint ret = NativeMethods.MsiSourceListClearAllEx( | ||
88 | this.installation.InstallationCode, | ||
89 | this.installation.UserSid, | ||
90 | this.installation.Context, | ||
91 | (uint) NativeMethods.SourceType.Media | (uint) this.installation.InstallationType); | ||
92 | |||
93 | if (ret != 0) | ||
94 | { | ||
95 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Checks if the specified media disk id exists in the list. | ||
101 | /// </summary> | ||
102 | /// <param name="diskId">disk id of the media to look for</param> | ||
103 | /// <returns>true if the media exists in the list, false otherwise</returns> | ||
104 | public bool Contains(int diskId) | ||
105 | { | ||
106 | foreach (MediaDisk mediaDisk in this) | ||
107 | { | ||
108 | if (mediaDisk.DiskId == diskId) | ||
109 | { | ||
110 | return true; | ||
111 | } | ||
112 | } | ||
113 | return false; | ||
114 | } | ||
115 | |||
116 | bool ICollection<MediaDisk>.Contains(MediaDisk mediaDisk) | ||
117 | { | ||
118 | return this.Contains(mediaDisk.DiskId); | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Copies the source media info from this list into an array. | ||
123 | /// </summary> | ||
124 | /// <param name="array">destination array to be filed</param> | ||
125 | /// <param name="arrayIndex">offset into the destination array where copying begins</param> | ||
126 | public void CopyTo(MediaDisk[] array, int arrayIndex) | ||
127 | { | ||
128 | foreach (MediaDisk mediaDisk in this) | ||
129 | { | ||
130 | array[arrayIndex++] = mediaDisk; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | /// <summary> | ||
135 | /// Removes a specified disk from the set of registered disks. | ||
136 | /// </summary> | ||
137 | /// <param name="diskId">ID of the disk to remove</param> | ||
138 | /// <remarks><p> | ||
139 | /// Win32 MSI API: | ||
140 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearmediadisk.asp">MsiSourceListClearMediaDisk</a> | ||
141 | /// </p></remarks> | ||
142 | public bool Remove(int diskId) | ||
143 | { | ||
144 | uint ret = NativeMethods.MsiSourceListClearMediaDisk( | ||
145 | this.installation.InstallationCode, | ||
146 | this.installation.UserSid, | ||
147 | this.installation.Context, | ||
148 | (uint) this.installation.InstallationType, | ||
149 | (uint) diskId); | ||
150 | |||
151 | if (ret != 0) | ||
152 | { | ||
153 | // TODO: Figure out when to return false. | ||
154 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
155 | } | ||
156 | return true; | ||
157 | } | ||
158 | |||
159 | bool ICollection<MediaDisk>.Remove(MediaDisk mediaDisk) | ||
160 | { | ||
161 | return this.Remove(mediaDisk.DiskId); | ||
162 | } | ||
163 | |||
164 | /// <summary> | ||
165 | /// Enumerates the source media in the source list of the patch or product installation. | ||
166 | /// </summary> | ||
167 | /// <remarks><p> | ||
168 | /// Win32 MSI API: | ||
169 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenummediadisks.asp">MsiSourceListEnumMediaDisks</a> | ||
170 | /// </p></remarks> | ||
171 | public IEnumerator<MediaDisk> GetEnumerator() | ||
172 | { | ||
173 | uint diskId; | ||
174 | StringBuilder volumeBuf = new StringBuilder(40); | ||
175 | uint volumeBufSize = (uint) volumeBuf.Capacity; | ||
176 | StringBuilder promptBuf = new StringBuilder(80); | ||
177 | uint promptBufSize = (uint) promptBuf.Capacity; | ||
178 | for (uint i = 0; true; i++) | ||
179 | { | ||
180 | uint ret = NativeMethods.MsiSourceListEnumMediaDisks( | ||
181 | this.installation.InstallationCode, | ||
182 | this.installation.UserSid, | ||
183 | this.installation.Context, | ||
184 | (uint) this.installation.InstallationType, | ||
185 | i, | ||
186 | out diskId, | ||
187 | volumeBuf, | ||
188 | ref volumeBufSize, | ||
189 | promptBuf, | ||
190 | ref promptBufSize); | ||
191 | |||
192 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
193 | { | ||
194 | volumeBuf.Capacity = (int) ++volumeBufSize; | ||
195 | promptBuf.Capacity = (int) ++promptBufSize; | ||
196 | |||
197 | ret = NativeMethods.MsiSourceListEnumMediaDisks( | ||
198 | this.installation.InstallationCode, | ||
199 | this.installation.UserSid, | ||
200 | this.installation.Context, | ||
201 | (uint) this.installation.InstallationType, | ||
202 | i, | ||
203 | out diskId, | ||
204 | volumeBuf, | ||
205 | ref volumeBufSize, | ||
206 | promptBuf, | ||
207 | ref promptBufSize); | ||
208 | } | ||
209 | |||
210 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
211 | { | ||
212 | break; | ||
213 | } | ||
214 | |||
215 | if (ret != 0) | ||
216 | { | ||
217 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
218 | } | ||
219 | |||
220 | yield return new MediaDisk((int) diskId, volumeBuf.ToString(), promptBuf.ToString()); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
225 | { | ||
226 | return this.GetEnumerator(); | ||
227 | } | ||
228 | } | ||
229 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs new file mode 100644 index 00000000..4dbff93f --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs | |||
@@ -0,0 +1,612 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Globalization; | ||
9 | using System.Runtime.InteropServices; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Provides access to summary information of a Windows Installer database. | ||
13 | /// </summary> | ||
14 | public class SummaryInfo : InstallerHandle | ||
15 | { | ||
16 | internal const int MAX_PROPERTIES = 20; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Gets a SummaryInfo object that can be used to examine, update, and add | ||
20 | /// properties to the summary information stream of a package or transform. | ||
21 | /// </summary> | ||
22 | /// <param name="packagePath">Path to the package (database) or transform</param> | ||
23 | /// <param name="enableWrite">True to reserve resources for writing summary information properties.</param> | ||
24 | /// <exception cref="FileNotFoundException">the package does not exist or could not be read</exception> | ||
25 | /// <exception cref="InstallerException">the package is an invalid format</exception> | ||
26 | /// <remarks><p> | ||
27 | /// The SummaryInfo object should be <see cref="InstallerHandle.Close"/>d after use. | ||
28 | /// It is best that the handle be closed manually as soon as it is no longer | ||
29 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
30 | /// </p><p> | ||
31 | /// Win32 MSI API: | ||
32 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a> | ||
33 | /// </p></remarks> | ||
34 | public SummaryInfo(string packagePath, bool enableWrite) | ||
35 | : base((IntPtr) SummaryInfo.OpenSummaryInfo(packagePath, enableWrite), true) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | internal SummaryInfo(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) | ||
40 | { | ||
41 | } | ||
42 | |||
43 | /// <summary>Gets or sets the Title summary information property.</summary> | ||
44 | /// <remarks><p> | ||
45 | /// The Title summary information property briefly describes the type of installer package. Phrases | ||
46 | /// such as "Installation Database" or "Transform" or "Patch" may be used for this property. | ||
47 | /// </p><p> | ||
48 | /// Win32 MSI APIs: | ||
49 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
50 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
51 | /// </p></remarks> | ||
52 | public string Title | ||
53 | { | ||
54 | get { return this[2]; } | ||
55 | set { this[2] = value; } | ||
56 | } | ||
57 | |||
58 | /// <summary>Gets or sets the Subject summary information property.</summary> | ||
59 | /// <remarks><p> | ||
60 | /// The Subject summary information property conveys to a file browser the product that can be installed using | ||
61 | /// the logic and data in this installer database. For example, the value of the summary property for | ||
62 | /// Microsoft Office 97 would be "Microsoft Office 97 Professional". This value is typically set from the | ||
63 | /// installer property ProductName. | ||
64 | /// </p><p> | ||
65 | /// Win32 MSI APIs: | ||
66 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
67 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
68 | /// </p></remarks> | ||
69 | public string Subject | ||
70 | { | ||
71 | get { return this[3]; } | ||
72 | set { this[3] = value; } | ||
73 | } | ||
74 | |||
75 | /// <summary>Gets or sets the Author summary information property.</summary> | ||
76 | /// <remarks><p> | ||
77 | /// The Author summary information property conveys to a file browser the manufacturer of the installation | ||
78 | /// database. This value is typically set from the installer property Manufacturer. | ||
79 | /// </p><p> | ||
80 | /// Win32 MSI APIs: | ||
81 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
82 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
83 | /// </p></remarks> | ||
84 | public string Author | ||
85 | { | ||
86 | get { return this[4]; } | ||
87 | set { this[4] = value; } | ||
88 | } | ||
89 | |||
90 | /// <summary>Gets or sets the Keywords summary information property.</summary> | ||
91 | /// <remarks><p> | ||
92 | /// The Keywords summary information property is used by file browsers to hold keywords that permit the | ||
93 | /// database file to be found in a keyword search. The set of keywords typically includes "Installer" as | ||
94 | /// well as product-specific keywords, and may be localized. | ||
95 | /// </p><p> | ||
96 | /// Win32 MSI APIs: | ||
97 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
98 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
99 | /// </p></remarks> | ||
100 | public string Keywords | ||
101 | { | ||
102 | get { return this[5]; } | ||
103 | set { this[5] = value; } | ||
104 | } | ||
105 | |||
106 | /// <summary>Gets or sets the Comments summary information property.</summary> | ||
107 | /// <remarks><p> | ||
108 | /// The Comments summary information property conveys the general purpose of the installer database. By convention, | ||
109 | /// the value for this summary property is set to the following: | ||
110 | /// </p><p> | ||
111 | /// "This installer database contains the logic and data required to install <product name>." | ||
112 | /// </p><p> | ||
113 | /// where <product name> is the name of the product being installed. In general the value for this summary | ||
114 | /// property only changes in the product name, nothing else. | ||
115 | /// </p><p> | ||
116 | /// Win32 MSI APIs: | ||
117 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
118 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
119 | /// </p></remarks> | ||
120 | public string Comments | ||
121 | { | ||
122 | get { return this[6]; } | ||
123 | set { this[6] = value; } | ||
124 | } | ||
125 | |||
126 | /// <summary>Gets or sets the Template summary information property.</summary> | ||
127 | /// <remarks><p> | ||
128 | /// The Template summary information propery indicates the platform and language versions supported by the database. | ||
129 | /// </p><p> | ||
130 | /// The syntax of the Template Summary property information is: | ||
131 | /// [platform property][,platform property][,...];[language id][,language id][,...] | ||
132 | /// </p><p> | ||
133 | /// For example, the following are all valid values for the Template Summary property: | ||
134 | /// <list type="bullet"> | ||
135 | /// <item>Intel;1033</item> | ||
136 | /// <item>Intel64;1033</item> | ||
137 | /// <item>;1033</item> | ||
138 | /// <item>;</item> | ||
139 | /// <item>Intel ;1033,2046</item> | ||
140 | /// <item>Intel64;1033,2046</item> | ||
141 | /// <item>Intel;0</item> | ||
142 | /// </list> | ||
143 | /// </p><p> | ||
144 | /// If this is a 64-bit Windows Installer, enter Intel64 in the Template summary information property. Note that an | ||
145 | /// installation package cannot have both the Intel and Intel64 properties set. | ||
146 | /// </p><p> | ||
147 | /// If the current platform does not match one of the platforms specified then the installer will not process the | ||
148 | /// package. Not specifying a platform implies that the package is platform-independent. | ||
149 | /// </p><p> | ||
150 | /// Entering 0 in the language ID field of the Template summary information property, or leaving this field empty, | ||
151 | /// indicates that the package is language neutral. | ||
152 | /// </p><p> | ||
153 | /// There are variations of this property depending on whether it is in a source installer database or a transform. | ||
154 | /// </p><p> | ||
155 | /// Source Installer Database - Only one language can be specified in a source installer database. Merge Modules are | ||
156 | /// the only packages that may have multiple languages. For more information, see Multiple Language Merge Modules. | ||
157 | /// </p><p> | ||
158 | /// Transform - In a transform file, only one language may be specified. The specified platform and language determine | ||
159 | /// whether a transform can be applied to a particular database. The platform property and the language property can | ||
160 | /// be left blank if no transform restriction relies on them to validate the transform. | ||
161 | /// </p><p> | ||
162 | /// This summary property is REQUIRED. | ||
163 | /// </p><p> | ||
164 | /// Win32 MSI APIs: | ||
165 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
166 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
167 | /// </p></remarks> | ||
168 | public string Template | ||
169 | { | ||
170 | get { return this[7]; } | ||
171 | set { this[7] = value; } | ||
172 | } | ||
173 | |||
174 | /// <summary>Gets or sets the LastSavedBy summary information property.</summary> | ||
175 | /// <remarks><p> | ||
176 | /// The installer sets the Last Saved By summary information property to the value of the LogonUser property during | ||
177 | /// an administrative installation. The installer never uses this property and a user never needs to modify it. | ||
178 | /// Developers of a database editing tool may use this property to track the last person to modify the database. | ||
179 | /// This property should be left set to null in a final shipping database. | ||
180 | /// </p><p> | ||
181 | /// In a transform, this summary property contains the platform and language ID(s) that a database should have | ||
182 | /// after it has been transformed. The property specifies to what the Template should be set in the new database. | ||
183 | /// </p><p> | ||
184 | /// Win32 MSI APIs: | ||
185 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
186 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
187 | /// </p></remarks> | ||
188 | public string LastSavedBy | ||
189 | { | ||
190 | get { return this[8]; } | ||
191 | set { this[8] = value; } | ||
192 | } | ||
193 | |||
194 | /// <summary>Gets or sets the RevisionNumber summary information property.</summary> | ||
195 | /// <remarks><p> | ||
196 | /// The Revision Number summary information property contains the package code for the installer package. The | ||
197 | /// package code is a unique identifier of the installer package. | ||
198 | /// </p><p> | ||
199 | /// The Revision Number summary information property of a patch package specifies the GUID patch code for | ||
200 | /// the patch. This is followed by a list of patch code GUIDs for obsolete patches that are removed when this | ||
201 | /// patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list. | ||
202 | /// </p><p> | ||
203 | /// The Revision Number summary information property of a transform package lists the product code GUIDs | ||
204 | /// and version of the new and original products and the upgrade code GUID. The list is separated with | ||
205 | /// semicolons as follows. | ||
206 | /// </p><p> | ||
207 | /// Original-Product-Code Original-Product-Version ; New-Product Code New-Product-Version; Upgrade-Code | ||
208 | /// </p><p> | ||
209 | /// This summary property is REQUIRED. | ||
210 | /// </p><p> | ||
211 | /// Win32 MSI APIs: | ||
212 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
213 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
214 | /// </p></remarks> | ||
215 | public string RevisionNumber | ||
216 | { | ||
217 | get { return this[9]; } | ||
218 | set { this[9] = value; } | ||
219 | } | ||
220 | |||
221 | /// <summary>Gets or sets the CreatingApp summary information property.</summary> | ||
222 | /// <remarks><p> | ||
223 | /// The CreatingApp summary information property conveys which application created the installer database. | ||
224 | /// In general the value for this summary property is the name of the software used to author this database. | ||
225 | /// </p><p> | ||
226 | /// Win32 MSI APIs: | ||
227 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
228 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
229 | /// </p></remarks> | ||
230 | public string CreatingApp | ||
231 | { | ||
232 | get { return this[18]; } | ||
233 | set { this[18] = value; } | ||
234 | } | ||
235 | |||
236 | /// <summary>Gets or sets the LastPrintTime summary information property.</summary> | ||
237 | /// <remarks><p> | ||
238 | /// The LastPrintTime summary information property can be set to the date and time during an administrative | ||
239 | /// installation to record when the administrative image was created. For non-administrative installations | ||
240 | /// this property is the same as the CreateTime summary information property. | ||
241 | /// </p><p> | ||
242 | /// Win32 MSI APIs: | ||
243 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
244 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
245 | /// </p></remarks> | ||
246 | public DateTime LastPrintTime | ||
247 | { | ||
248 | get { return (DateTime) this[11, typeof(DateTime)]; } | ||
249 | set { this[11, typeof(DateTime)] = value; } | ||
250 | } | ||
251 | |||
252 | /// <summary>Gets or sets the CreateTime summary information property.</summary> | ||
253 | /// <remarks><p> | ||
254 | /// The CreateTime summary information property conveys when the installer database was created. | ||
255 | /// </p><p> | ||
256 | /// Win32 MSI APIs: | ||
257 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
258 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
259 | /// </p></remarks> | ||
260 | public DateTime CreateTime | ||
261 | { | ||
262 | get { return (DateTime) this[12, typeof(DateTime)]; } | ||
263 | set { this[12, typeof(DateTime)] = value; } | ||
264 | } | ||
265 | |||
266 | /// <summary>Gets or sets the LastSaveTime summary information property.</summary> | ||
267 | /// <remarks><p> | ||
268 | /// The LastSaveTime summary information property conveys when the last time the installer database was | ||
269 | /// modified. Each time a user changes an installation the value for this summary property is updated to | ||
270 | /// the current system time/date at the time the installer database was saved. Initially the value for | ||
271 | /// this summary property is set to null to indicate that no changes have yet been made. | ||
272 | /// </p><p> | ||
273 | /// Win32 MSI APIs: | ||
274 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
275 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
276 | /// </p></remarks> | ||
277 | public DateTime LastSaveTime | ||
278 | { | ||
279 | get { return (DateTime) this[13, typeof(DateTime)]; } | ||
280 | set { this[13, typeof(DateTime)] = value; } | ||
281 | } | ||
282 | |||
283 | /// <summary>Gets or sets the CodePage summary information property.</summary> | ||
284 | /// <remarks><p> | ||
285 | /// The Codepage summary information property is the numeric value of the ANSI code page used for any | ||
286 | /// strings that are stored in the summary information. Note that this is not the same code page for | ||
287 | /// strings in the installation database. The Codepage summary information property is used to translate | ||
288 | /// the strings in the summary information into Unicode when calling the Unicode API functions. The | ||
289 | /// Codepage summary information property must be set before any string properties are set in the | ||
290 | /// summary information. | ||
291 | /// </p><p> | ||
292 | /// Win32 MSI APIs: | ||
293 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
294 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
295 | /// </p></remarks> | ||
296 | public short CodePage | ||
297 | { | ||
298 | get { return (short) this[1, typeof(short)]; } | ||
299 | set { this[1, typeof(short)] = value; } | ||
300 | } | ||
301 | |||
302 | /// <summary>Gets or sets the PageCount summary information property.</summary> | ||
303 | /// <remarks><p> | ||
304 | /// For an installation package, the PageCount summary information property contains the minimum | ||
305 | /// installer version required. For Windows Installer version 1.0, this property must be set to the | ||
306 | /// integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200. | ||
307 | /// </p><p> | ||
308 | /// For a transform package, the PageCount summary information property contains minimum installer | ||
309 | /// version required to process the transform. Set to the greater of the two PageCount summary information | ||
310 | /// property values belonging to the databases used to generate the transform. | ||
311 | /// </p><p> | ||
312 | /// The PageCount summary information property is set to null in patch packages. | ||
313 | /// </p><p> | ||
314 | /// This summary property is REQUIRED. | ||
315 | /// </p><p> | ||
316 | /// Win32 MSI APIs: | ||
317 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
318 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
319 | /// </p></remarks> | ||
320 | public int PageCount | ||
321 | { | ||
322 | get { return (int) this[14, typeof(int)]; } | ||
323 | set { this[14, typeof(int)] = value; } | ||
324 | } | ||
325 | |||
326 | /// <summary>Gets or sets the WordCount summary information property.</summary> | ||
327 | /// <remarks><p> | ||
328 | /// The WordCount summary information property indicates the type of source file image. If this property is | ||
329 | /// not present, it defaults to 0. Note that this property is stored in place of the standard Count property. | ||
330 | /// </p><p> | ||
331 | /// This property is a bit field. New bits may be added in the future. At present the following bits are | ||
332 | /// available: | ||
333 | /// <list type="bullet"> | ||
334 | /// <item>Bit 0: 0 = long file names, 1 = short file names</item> | ||
335 | /// <item>Bit 1: 0 = source is uncompressed, 1 = source is compressed</item> | ||
336 | /// <item>Bit 2: 0 = source is original media, 1 = source is administrative installation</item> | ||
337 | /// <item>[MSI 4.0] Bit 3: 0 = elevated privileges can be required to install, 1 = elevated privileges are not required to install</item> | ||
338 | /// </list> | ||
339 | /// </p><p> | ||
340 | /// These are combined to give the WordCount summary information property one of the following values | ||
341 | /// indicating a type of source file image: | ||
342 | /// <list type="bullet"> | ||
343 | /// <item>0 - Original source using long file names. Matches tree in Directory table.</item> | ||
344 | /// <item>1 - Original source using short file names. Matches tree in Directory table.</item> | ||
345 | /// <item>2 - Compressed source files using long file names. Matches cabinets and files in the Media table.</item> | ||
346 | /// <item>3 - Compressed source files using short file names. Matches cabinets and files in the Media table.</item> | ||
347 | /// <item>4 - Administrative image using long file names. Matches tree in Directory table.</item> | ||
348 | /// <item>5 - Administrative image using short file names. Matches tree in Directory table.</item> | ||
349 | /// </list> | ||
350 | /// </p><p> | ||
351 | /// Note that if the package is marked as compressed (bit 1 is set), the installer only installs files | ||
352 | /// located at the root of the source. In this case, even files marked as uncompressed in the File table must | ||
353 | /// be located at the root to be installed. To specify a source image that has both a cabinet file (compressed | ||
354 | /// files) and uncompressed files that match the tree in the Directory table, mark the package as uncompressed | ||
355 | /// by leaving bit 1 unset (value=0) in the WordCount summary information property and set | ||
356 | /// <see cref="FileAttributes.Compressed"/> (value=16384) in the Attributes column of the File table | ||
357 | /// for each file in the cabinet. | ||
358 | /// </p><p> | ||
359 | /// For a patch package, the WordCount summary information property specifies the patch engine that was used | ||
360 | /// to create the patch files. The default value is 1 and indicates that MSPATCH was used to create the patch | ||
361 | /// A value of "2" means that the patch is using smaller, optimized, files available only with Windows Installer | ||
362 | /// version 1.2 or later. A patch with a WordCount of "2" fails immediately if used with a Windows Installer | ||
363 | /// version earlier than 1.2. A patch with a WordCount of "3" fails immediately if used with a Windows Installer | ||
364 | /// version earlier than 2.0. | ||
365 | /// </p><p> | ||
366 | /// This summary property is REQUIRED. | ||
367 | /// </p><p> | ||
368 | /// Win32 MSI APIs: | ||
369 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
370 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
371 | /// </p></remarks> | ||
372 | public int WordCount | ||
373 | { | ||
374 | get { return (int) this[15, typeof(int)]; } | ||
375 | set { this[15, typeof(int)] = value; } | ||
376 | } | ||
377 | |||
378 | /// <summary>Gets or sets the CharacterCount summary information property.</summary> | ||
379 | /// <remarks><p> | ||
380 | /// The CharacterCount summary information property is only used in transforms. This part of the summary | ||
381 | /// information stream is divided into two 16-bit words. The upper word contains the transform validation | ||
382 | /// flags. The lower word contains the transform error condition flags. | ||
383 | /// </p><p> | ||
384 | /// Win32 MSI APIs: | ||
385 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
386 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
387 | /// </p></remarks> | ||
388 | public int CharacterCount | ||
389 | { | ||
390 | get { return (int) this[16, typeof(int)]; } | ||
391 | set { this[16, typeof(int)] = value; } | ||
392 | } | ||
393 | |||
394 | /// <summary>Gets or sets the Security summary information property.</summary> | ||
395 | /// <remarks><p> | ||
396 | /// The Security summary information property conveys whether the package should be opened as read-only. The database | ||
397 | /// editing tool should not modify a read-only enforced database and should issue a warning at attempts to modify a | ||
398 | /// read-only recommended database. The following values of this property are applicable to Windows Installer files: | ||
399 | /// <list type="bullet"> | ||
400 | /// <item>0 - no restriction</item> | ||
401 | /// <item>2 - read only recommended</item> | ||
402 | /// <item>4 - read only enforced</item> | ||
403 | /// </list> | ||
404 | /// </p><p> | ||
405 | /// This property should be set to read-only recommended (2) for an installation database and to read-only | ||
406 | /// enforced (4) for a transform or patch. | ||
407 | /// </p><p> | ||
408 | /// Win32 MSI APIs: | ||
409 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>, | ||
410 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a> | ||
411 | /// </p></remarks> | ||
412 | public int Security | ||
413 | { | ||
414 | get { return (int) this[19, typeof(int)]; } | ||
415 | set { this[19, typeof(int)] = value; } | ||
416 | } | ||
417 | |||
418 | private object this[uint property, Type type] | ||
419 | { | ||
420 | get | ||
421 | { | ||
422 | uint dataType; | ||
423 | StringBuilder stringValue = new StringBuilder(""); | ||
424 | uint bufSize = 0; | ||
425 | int intValue; | ||
426 | long timeValue = 0; | ||
427 | |||
428 | uint ret = RemotableNativeMethods.MsiSummaryInfoGetProperty( | ||
429 | (int) this.Handle, | ||
430 | property, | ||
431 | out dataType, | ||
432 | out intValue, | ||
433 | ref timeValue, | ||
434 | stringValue, | ||
435 | ref bufSize); | ||
436 | if (ret != 0 && dataType != (uint) VarEnum.VT_LPSTR) | ||
437 | { | ||
438 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
439 | } | ||
440 | |||
441 | switch ((VarEnum) dataType) | ||
442 | { | ||
443 | case VarEnum.VT_EMPTY: | ||
444 | { | ||
445 | if (type == typeof(DateTime)) | ||
446 | { | ||
447 | return DateTime.MinValue; | ||
448 | } | ||
449 | else if (type == typeof(string)) | ||
450 | { | ||
451 | return String.Empty; | ||
452 | } | ||
453 | else if (type == typeof(short)) | ||
454 | { | ||
455 | return (short) 0; | ||
456 | } | ||
457 | else | ||
458 | { | ||
459 | return (int) 0; | ||
460 | } | ||
461 | } | ||
462 | |||
463 | case VarEnum.VT_LPSTR: | ||
464 | { | ||
465 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
466 | { | ||
467 | stringValue.Capacity = (int) ++bufSize; | ||
468 | ret = RemotableNativeMethods.MsiSummaryInfoGetProperty( | ||
469 | (int) this.Handle, | ||
470 | property, | ||
471 | out dataType, | ||
472 | out intValue, | ||
473 | ref timeValue, | ||
474 | stringValue, | ||
475 | ref bufSize); | ||
476 | } | ||
477 | if (ret != 0) | ||
478 | { | ||
479 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
480 | } | ||
481 | return stringValue.ToString(); | ||
482 | } | ||
483 | |||
484 | case VarEnum.VT_I2: | ||
485 | case VarEnum.VT_I4: | ||
486 | { | ||
487 | if (type == typeof(string)) | ||
488 | { | ||
489 | return intValue.ToString(CultureInfo.InvariantCulture); | ||
490 | } | ||
491 | else if (type == typeof(short)) | ||
492 | { | ||
493 | return (short) intValue; | ||
494 | } | ||
495 | else | ||
496 | { | ||
497 | return intValue; | ||
498 | } | ||
499 | } | ||
500 | |||
501 | case VarEnum.VT_FILETIME: | ||
502 | { | ||
503 | if (type == typeof(string)) | ||
504 | { | ||
505 | return DateTime.FromFileTime(timeValue).ToString(CultureInfo.InvariantCulture); | ||
506 | } | ||
507 | else | ||
508 | { | ||
509 | return DateTime.FromFileTime(timeValue); | ||
510 | } | ||
511 | } | ||
512 | |||
513 | default: | ||
514 | { | ||
515 | throw new InstallerException(); | ||
516 | } | ||
517 | } | ||
518 | } | ||
519 | |||
520 | set | ||
521 | { | ||
522 | uint dataType = (uint) VarEnum.VT_NULL; | ||
523 | string stringValue = ""; | ||
524 | int intValue = 0; | ||
525 | long timeValue = 0; | ||
526 | |||
527 | if (type == typeof(short)) | ||
528 | { | ||
529 | dataType = (uint) VarEnum.VT_I2; | ||
530 | intValue = (int)(short) value; // Double cast because value is a *boxed* short. | ||
531 | } | ||
532 | else if (type == typeof(int)) | ||
533 | { | ||
534 | dataType = (uint) VarEnum.VT_I4; | ||
535 | intValue = (int) value; | ||
536 | } | ||
537 | else if (type == typeof(string)) | ||
538 | { | ||
539 | dataType = (uint) VarEnum.VT_LPSTR; | ||
540 | stringValue = (string) value; | ||
541 | } | ||
542 | else // (type == typeof(DateTime)) | ||
543 | { | ||
544 | dataType = (uint) VarEnum.VT_FILETIME; | ||
545 | timeValue = ((DateTime) value).ToFileTime(); | ||
546 | } | ||
547 | |||
548 | uint ret = NativeMethods.MsiSummaryInfoSetProperty( | ||
549 | (int) this.Handle, | ||
550 | property, | ||
551 | dataType, | ||
552 | intValue, | ||
553 | ref timeValue, | ||
554 | stringValue); | ||
555 | if (ret != 0) | ||
556 | { | ||
557 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
558 | } | ||
559 | } | ||
560 | } | ||
561 | |||
562 | private string this[uint property] | ||
563 | { | ||
564 | get { return (string) this[property, typeof(string)]; } | ||
565 | set { this[property, typeof(string)] = value; } | ||
566 | } | ||
567 | |||
568 | /// <summary> | ||
569 | /// Formats and writes the previously stored properties into the standard summary information stream. | ||
570 | /// </summary> | ||
571 | /// <exception cref="InstallerException">The stream cannot be successfully written.</exception> | ||
572 | /// <remarks><p> | ||
573 | /// This method may only be called once after all the property values have been set. Properties may | ||
574 | /// still be read after the stream is written. | ||
575 | /// </p><p> | ||
576 | /// Win32 MSI API: | ||
577 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfopersist.asp">MsiSummaryInfoPersist</a> | ||
578 | /// </p></remarks> | ||
579 | public void Persist() | ||
580 | { | ||
581 | uint ret = NativeMethods.MsiSummaryInfoPersist((int) this.Handle); | ||
582 | if (ret != 0) | ||
583 | { | ||
584 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
585 | } | ||
586 | } | ||
587 | |||
588 | private static int OpenSummaryInfo(string packagePath, bool enableWrite) | ||
589 | { | ||
590 | int summaryInfoHandle; | ||
591 | int maxProperties = !enableWrite ? 0 : SummaryInfo.MAX_PROPERTIES; | ||
592 | uint ret = RemotableNativeMethods.MsiGetSummaryInformation( | ||
593 | 0, | ||
594 | packagePath, | ||
595 | (uint) maxProperties, | ||
596 | out summaryInfoHandle); | ||
597 | if (ret != 0) | ||
598 | { | ||
599 | if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || | ||
600 | ret == (uint) NativeMethods.Error.ACCESS_DENIED) | ||
601 | { | ||
602 | throw new FileNotFoundException(null, packagePath); | ||
603 | } | ||
604 | else | ||
605 | { | ||
606 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
607 | } | ||
608 | } | ||
609 | return summaryInfoHandle; | ||
610 | } | ||
611 | } | ||
612 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs new file mode 100644 index 00000000..95176ea0 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs | |||
@@ -0,0 +1,192 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Text; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Contains information about all the tables in a Windows Installer database. | ||
11 | /// </summary> | ||
12 | public class TableCollection : ICollection<TableInfo> | ||
13 | { | ||
14 | private Database db; | ||
15 | |||
16 | internal TableCollection(Database db) | ||
17 | { | ||
18 | this.db = db; | ||
19 | } | ||
20 | |||
21 | /// <summary> | ||
22 | /// Gets the number of tables in the database. | ||
23 | /// </summary> | ||
24 | public int Count | ||
25 | { | ||
26 | get | ||
27 | { | ||
28 | return this.GetTables().Count; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Gets a boolean value indicating whether the collection is read-only. | ||
34 | /// A TableCollection is read-only when the database is read-only. | ||
35 | /// </summary> | ||
36 | /// <value>read-only status of the collection</value> | ||
37 | public bool IsReadOnly | ||
38 | { | ||
39 | get | ||
40 | { | ||
41 | return this.db.IsReadOnly; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets information about a given table. | ||
47 | /// </summary> | ||
48 | /// <param name="table">case-sensitive name of the table</param> | ||
49 | /// <returns>information about the requested table, or null if the table does not exist in the database</returns> | ||
50 | public TableInfo this[string table] | ||
51 | { | ||
52 | get | ||
53 | { | ||
54 | if (String.IsNullOrEmpty(table)) | ||
55 | { | ||
56 | throw new ArgumentNullException("table"); | ||
57 | } | ||
58 | |||
59 | if (!this.Contains(table)) | ||
60 | { | ||
61 | return null; | ||
62 | } | ||
63 | |||
64 | return new TableInfo(this.db, table); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | /// <summary> | ||
69 | /// Adds a new table to the database. | ||
70 | /// </summary> | ||
71 | /// <param name="item">information about the table to be added</param> | ||
72 | /// <exception cref="InvalidOperationException">a table with the same name already exists in the database</exception> | ||
73 | public void Add(TableInfo item) | ||
74 | { | ||
75 | if (item == null) | ||
76 | { | ||
77 | throw new ArgumentNullException("item"); | ||
78 | } | ||
79 | |||
80 | if (this.Contains(item.Name)) | ||
81 | { | ||
82 | throw new InvalidOperationException(); | ||
83 | } | ||
84 | |||
85 | this.db.Execute(item.SqlCreateString); | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Removes all tables (and all data) from the database. | ||
90 | /// </summary> | ||
91 | public void Clear() | ||
92 | { | ||
93 | foreach (string table in this.GetTables()) | ||
94 | { | ||
95 | this.Remove(table); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Checks if the database contains a table with the given name. | ||
101 | /// </summary> | ||
102 | /// <param name="item">case-sensitive name of the table to search for</param> | ||
103 | /// <returns>True if the table exists, false otherwise.</returns> | ||
104 | public bool Contains(string item) | ||
105 | { | ||
106 | if (String.IsNullOrEmpty(item)) | ||
107 | { | ||
108 | throw new ArgumentNullException("item"); | ||
109 | } | ||
110 | uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.db.Handle, item); | ||
111 | if (ret == 3) // MSICONDITION_ERROR | ||
112 | { | ||
113 | throw new InstallerException(); | ||
114 | } | ||
115 | return ret != 2; // MSICONDITION_NONE | ||
116 | } | ||
117 | |||
118 | bool ICollection<TableInfo>.Contains(TableInfo item) | ||
119 | { | ||
120 | return this.Contains(item.Name); | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Copies the table information from this collection into an array. | ||
125 | /// </summary> | ||
126 | /// <param name="array">destination array to be filed</param> | ||
127 | /// <param name="arrayIndex">offset into the destination array where copying begins</param> | ||
128 | public void CopyTo(TableInfo[] array, int arrayIndex) | ||
129 | { | ||
130 | if (array == null) | ||
131 | { | ||
132 | throw new ArgumentNullException("array"); | ||
133 | } | ||
134 | |||
135 | foreach (string table in this.GetTables()) | ||
136 | { | ||
137 | array[arrayIndex++] = new TableInfo(this.db, table); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | /// <summary> | ||
142 | /// Removes a table from the database. | ||
143 | /// </summary> | ||
144 | /// <param name="item">case-sensitive name of the table to be removed</param> | ||
145 | /// <returns>true if the table was removed, false if the table did not exist</returns> | ||
146 | public bool Remove(string item) | ||
147 | { | ||
148 | if (String.IsNullOrEmpty(item)) | ||
149 | { | ||
150 | throw new ArgumentNullException("item"); | ||
151 | } | ||
152 | |||
153 | if (!this.Contains(item)) | ||
154 | { | ||
155 | return false; | ||
156 | } | ||
157 | this.db.Execute("DROP TABLE `{0}`", item); | ||
158 | return true; | ||
159 | } | ||
160 | |||
161 | bool ICollection<TableInfo>.Remove(TableInfo item) | ||
162 | { | ||
163 | if (item == null) | ||
164 | { | ||
165 | throw new ArgumentNullException("item"); | ||
166 | } | ||
167 | |||
168 | return this.Remove(item.Name); | ||
169 | } | ||
170 | |||
171 | /// <summary> | ||
172 | /// Enumerates the tables in the database. | ||
173 | /// </summary> | ||
174 | public IEnumerator<TableInfo> GetEnumerator() | ||
175 | { | ||
176 | foreach (string table in this.GetTables()) | ||
177 | { | ||
178 | yield return new TableInfo(this.db, table); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
183 | { | ||
184 | return this.GetEnumerator(); | ||
185 | } | ||
186 | |||
187 | private IList<string> GetTables() | ||
188 | { | ||
189 | return this.db.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); | ||
190 | } | ||
191 | } | ||
192 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs new file mode 100644 index 00000000..e5a850b0 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs | |||
@@ -0,0 +1,264 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Collections.ObjectModel; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Defines a table in an installation database. | ||
12 | /// </summary> | ||
13 | public class TableInfo | ||
14 | { | ||
15 | private string name; | ||
16 | private ColumnCollection columns; | ||
17 | private ReadOnlyCollection<string> primaryKeys; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Creates a table definition. | ||
21 | /// </summary> | ||
22 | /// <param name="name">Name of the table.</param> | ||
23 | /// <param name="columns">Columns in the table.</param> | ||
24 | /// <param name="primaryKeys">The primary keys of the table.</param> | ||
25 | public TableInfo(string name, ICollection<ColumnInfo> columns, IList<string> primaryKeys) | ||
26 | { | ||
27 | if (String.IsNullOrEmpty(name)) | ||
28 | { | ||
29 | throw new ArgumentNullException("name"); | ||
30 | } | ||
31 | |||
32 | if (columns == null || columns.Count == 0) | ||
33 | { | ||
34 | throw new ArgumentNullException("columns"); | ||
35 | } | ||
36 | |||
37 | if (primaryKeys == null || primaryKeys.Count == 0) | ||
38 | { | ||
39 | throw new ArgumentNullException("primaryKeys"); | ||
40 | } | ||
41 | |||
42 | this.name = name; | ||
43 | this.columns = new ColumnCollection(columns); | ||
44 | this.primaryKeys = new List<string>(primaryKeys).AsReadOnly(); | ||
45 | foreach (string primaryKey in this.primaryKeys) | ||
46 | { | ||
47 | if (!this.columns.Contains(primaryKey)) | ||
48 | { | ||
49 | throw new ArgumentOutOfRangeException("primaryKeys"); | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | internal TableInfo(Database db, string name) | ||
55 | { | ||
56 | if (db == null) | ||
57 | { | ||
58 | throw new ArgumentNullException("db"); | ||
59 | } | ||
60 | |||
61 | if (String.IsNullOrEmpty(name)) | ||
62 | { | ||
63 | throw new ArgumentNullException("name"); | ||
64 | } | ||
65 | |||
66 | this.name = name; | ||
67 | |||
68 | using (View columnsView = db.OpenView("SELECT * FROM `{0}`", name)) | ||
69 | { | ||
70 | this.columns = new ColumnCollection(columnsView); | ||
71 | } | ||
72 | |||
73 | this.primaryKeys = new ReadOnlyCollection<string>( | ||
74 | TableInfo.GetTablePrimaryKeys(db, name)); | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// Gets the name of the table. | ||
79 | /// </summary> | ||
80 | public string Name | ||
81 | { | ||
82 | get | ||
83 | { | ||
84 | return this.name; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Gets information about the columns in this table. | ||
90 | /// </summary> | ||
91 | /// <remarks><p> | ||
92 | /// This property queries the database every time it is called, | ||
93 | /// to ensure the returned values are up-to-date. For best performance, | ||
94 | /// hold onto the returned collection if using it more than once. | ||
95 | /// </p></remarks> | ||
96 | public ColumnCollection Columns | ||
97 | { | ||
98 | get | ||
99 | { | ||
100 | return this.columns; | ||
101 | } | ||
102 | } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Gets the names of the columns that are primary keys of the table. | ||
106 | /// </summary> | ||
107 | public IList<string> PrimaryKeys | ||
108 | { | ||
109 | get | ||
110 | { | ||
111 | return this.primaryKeys; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | /// <summary> | ||
116 | /// Gets an SQL CREATE string that can be used to create the table. | ||
117 | /// </summary> | ||
118 | public string SqlCreateString | ||
119 | { | ||
120 | get | ||
121 | { | ||
122 | StringBuilder s = new StringBuilder("CREATE TABLE `"); | ||
123 | s.Append(this.name); | ||
124 | s.Append("` ("); | ||
125 | int count = 0; | ||
126 | foreach (ColumnInfo col in this.Columns) | ||
127 | { | ||
128 | if (count > 0) | ||
129 | { | ||
130 | s.Append(", "); | ||
131 | } | ||
132 | s.Append(col.SqlCreateString); | ||
133 | count++; | ||
134 | } | ||
135 | s.Append(" PRIMARY KEY "); | ||
136 | count = 0; | ||
137 | foreach (string key in this.PrimaryKeys) | ||
138 | { | ||
139 | if (count > 0) | ||
140 | { | ||
141 | s.Append(", "); | ||
142 | } | ||
143 | s.AppendFormat("`{0}`", key); | ||
144 | count++; | ||
145 | } | ||
146 | s.Append(')'); | ||
147 | return s.ToString(); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | /// <summary> | ||
152 | /// Gets an SQL INSERT string that can be used insert a new record into the table. | ||
153 | /// </summary> | ||
154 | /// <remarks><p> | ||
155 | /// The values are expressed as question-mark tokens, to be supplied by the record. | ||
156 | /// </p></remarks> | ||
157 | public string SqlInsertString | ||
158 | { | ||
159 | get | ||
160 | { | ||
161 | StringBuilder s = new StringBuilder("INSERT INTO `"); | ||
162 | s.Append(this.name); | ||
163 | s.Append("` ("); | ||
164 | int count = 0; | ||
165 | foreach (ColumnInfo col in this.Columns) | ||
166 | { | ||
167 | if (count > 0) | ||
168 | { | ||
169 | s.Append(", "); | ||
170 | } | ||
171 | |||
172 | s.AppendFormat("`{0}`", col.Name); | ||
173 | count++; | ||
174 | } | ||
175 | s.Append(") VALUES ("); | ||
176 | while (count > 0) | ||
177 | { | ||
178 | count--; | ||
179 | s.Append("?"); | ||
180 | |||
181 | if (count > 0) | ||
182 | { | ||
183 | s.Append(", "); | ||
184 | } | ||
185 | } | ||
186 | s.Append(')'); | ||
187 | return s.ToString(); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | /// <summary> | ||
192 | /// Gets an SQL SELECT string that can be used to select all columns of the table. | ||
193 | /// </summary> | ||
194 | /// <remarks><p> | ||
195 | /// The columns are listed explicitly in the SELECT string, as opposed to using "SELECT *". | ||
196 | /// </p></remarks> | ||
197 | public string SqlSelectString | ||
198 | { | ||
199 | get | ||
200 | { | ||
201 | StringBuilder s = new StringBuilder("SELECT "); | ||
202 | int count = 0; | ||
203 | foreach (ColumnInfo col in this.Columns) | ||
204 | { | ||
205 | if (count > 0) s.Append(", "); | ||
206 | s.AppendFormat("`{0}`", col.Name); | ||
207 | count++; | ||
208 | } | ||
209 | s.AppendFormat(" FROM `{0}`", this.Name); | ||
210 | return s.ToString(); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | /// <summary> | ||
215 | /// Gets a string representation of the table. | ||
216 | /// </summary> | ||
217 | /// <returns>The name of the table.</returns> | ||
218 | public override string ToString() | ||
219 | { | ||
220 | return this.name; | ||
221 | } | ||
222 | |||
223 | private static IList<string> GetTablePrimaryKeys(Database db, string table) | ||
224 | { | ||
225 | if (table == "_Tables") | ||
226 | { | ||
227 | return new string[] { "Name" }; | ||
228 | } | ||
229 | else if (table == "_Columns") | ||
230 | { | ||
231 | return new string[] { "Table", "Number" }; | ||
232 | } | ||
233 | else if (table == "_Storages") | ||
234 | { | ||
235 | return new string[] { "Name" }; | ||
236 | } | ||
237 | else if (table == "_Streams") | ||
238 | { | ||
239 | return new string[] { "Name" }; | ||
240 | } | ||
241 | else | ||
242 | { | ||
243 | int hrec; | ||
244 | uint ret = RemotableNativeMethods.MsiDatabaseGetPrimaryKeys( | ||
245 | (int) db.Handle, table, out hrec); | ||
246 | if (ret != 0) | ||
247 | { | ||
248 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
249 | } | ||
250 | |||
251 | using (Record rec = new Record((IntPtr) hrec, true, null)) | ||
252 | { | ||
253 | string[] keys = new string[rec.FieldCount]; | ||
254 | for (int i = 0; i < keys.Length; i++) | ||
255 | { | ||
256 | keys[i] = rec.GetString(i + 1); | ||
257 | } | ||
258 | |||
259 | return keys; | ||
260 | } | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs new file mode 100644 index 00000000..798dc79e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs | |||
@@ -0,0 +1,201 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | using System.Runtime.InteropServices; | ||
9 | using System.Threading; | ||
10 | |||
11 | /// <summary> | ||
12 | /// [MSI 4.5] Handle to a multi-session install transaction. | ||
13 | /// </summary> | ||
14 | /// <remarks><p> | ||
15 | /// Win32 MSI APIs: | ||
16 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a> | ||
17 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a> | ||
18 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> | ||
19 | /// </p></remarks> | ||
20 | public class Transaction : InstallerHandle | ||
21 | { | ||
22 | private string name; | ||
23 | private IntPtr ownerChangeEvent; | ||
24 | private IList<EventHandler<EventArgs>> ownerChangeListeners; | ||
25 | |||
26 | /// <summary> | ||
27 | /// [MSI 4.5] Begins transaction processing of a multi-package installation. | ||
28 | /// </summary> | ||
29 | /// <param name="name">Name of the multi-package installation.</param> | ||
30 | /// <param name="attributes">Select optional behavior when beginning the transaction.</param> | ||
31 | /// <exception cref="InstallerException">The transaction could not be initialized.</exception> | ||
32 | /// <remarks><p> | ||
33 | /// Win32 MSI API: | ||
34 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a> | ||
35 | /// </p></remarks> | ||
36 | public Transaction(string name, TransactionAttributes attributes) | ||
37 | : this(name, Transaction.Begin(name, attributes), true) | ||
38 | { | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Internal constructor. | ||
43 | /// </summary> | ||
44 | /// <remarks> | ||
45 | /// The second parameter is an array in order to receive multiple values from the initialization method. | ||
46 | /// </remarks> | ||
47 | private Transaction(string name, IntPtr[] handles, bool ownsHandle) | ||
48 | : base(handles[0], ownsHandle) | ||
49 | { | ||
50 | this.name = name; | ||
51 | this.ownerChangeEvent = handles[1]; | ||
52 | this.ownerChangeListeners = new List<EventHandler<EventArgs>>(); | ||
53 | } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Creates a new Transaction object from an integer handle. | ||
57 | /// </summary> | ||
58 | /// <param name="handle">Integer transaction handle</param> | ||
59 | /// <param name="ownsHandle">true to close the handle when this object is disposed</param> | ||
60 | public static Transaction FromHandle(IntPtr handle, bool ownsHandle) | ||
61 | { | ||
62 | return new Transaction(handle.ToString(), new IntPtr[] { handle, IntPtr.Zero }, ownsHandle); | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Gets the name of the transaction. | ||
67 | /// </summary> | ||
68 | public string Name | ||
69 | { | ||
70 | get | ||
71 | { | ||
72 | return name; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Notifies listeners when the process that owns the transaction changed. | ||
78 | /// </summary> | ||
79 | public event EventHandler<EventArgs> OwnerChanged | ||
80 | { | ||
81 | add | ||
82 | { | ||
83 | this.ownerChangeListeners.Add(value); | ||
84 | |||
85 | if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count == 1) | ||
86 | { | ||
87 | new Thread(this.WaitForOwnerChange).Start(); | ||
88 | } | ||
89 | } | ||
90 | remove | ||
91 | { | ||
92 | this.ownerChangeListeners.Remove(value); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | private void OnOwnerChanged() | ||
97 | { | ||
98 | EventArgs e = new EventArgs(); | ||
99 | foreach (EventHandler<EventArgs> handler in this.ownerChangeListeners) | ||
100 | { | ||
101 | handler(this, e); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | private void WaitForOwnerChange() | ||
106 | { | ||
107 | int ret = NativeMethods.WaitForSingleObject(this.ownerChangeEvent, -1); | ||
108 | if (ret == 0) | ||
109 | { | ||
110 | this.OnOwnerChanged(); | ||
111 | } | ||
112 | else | ||
113 | { | ||
114 | throw new InstallerException(); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | /// <summary> | ||
119 | /// Makes the current process the owner of the multi-package installation transaction. | ||
120 | /// </summary> | ||
121 | /// <param name="attributes">Select optional behavior when joining the transaction.</param> | ||
122 | /// <exception cref="InvalidHandleException">The transaction handle is not valid.</exception> | ||
123 | /// <exception cref="InstallerException">The transaction could not be joined.</exception> | ||
124 | /// <remarks><p> | ||
125 | /// Win32 MSI API: | ||
126 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a> | ||
127 | /// </p></remarks> | ||
128 | public void Join(TransactionAttributes attributes) | ||
129 | { | ||
130 | IntPtr hChangeOfOwnerEvent; | ||
131 | uint ret = NativeMethods.MsiJoinTransaction((int) this.Handle, (int) attributes, out hChangeOfOwnerEvent); | ||
132 | if (ret != 0) | ||
133 | { | ||
134 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
135 | } | ||
136 | |||
137 | this.ownerChangeEvent = hChangeOfOwnerEvent; | ||
138 | if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count >= 1) | ||
139 | { | ||
140 | new Thread(this.WaitForOwnerChange).Start(); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | /// <summary> | ||
145 | /// Ends the install transaction and commits all changes to the system belonging to the transaction. | ||
146 | /// </summary> | ||
147 | /// <exception cref="InstallerException">The transaction could not be committed.</exception> | ||
148 | /// <remarks><p> | ||
149 | /// Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language | ||
150 | /// runtime assemblies. Deletes the rollback script, and after using this option, the transaction's | ||
151 | /// changes can no longer be undone with a Rollback Installation. | ||
152 | /// </p><p> | ||
153 | /// This method can only be called by the current owner of the transaction. | ||
154 | /// </p><p> | ||
155 | /// Win32 MSI API: | ||
156 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> | ||
157 | /// </p></remarks> | ||
158 | public void Commit() | ||
159 | { | ||
160 | this.End(true); | ||
161 | } | ||
162 | |||
163 | /// <summary> | ||
164 | /// Ends the install transaction and undoes changes to the system belonging to the transaction. | ||
165 | /// </summary> | ||
166 | /// <exception cref="InstallerException">The transaction could not be rolled back.</exception> | ||
167 | /// <remarks><p> | ||
168 | /// This method can only be called by the current owner of the transaction. | ||
169 | /// </p><p> | ||
170 | /// Win32 MSI API: | ||
171 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> | ||
172 | /// </p></remarks> | ||
173 | public void Rollback() | ||
174 | { | ||
175 | this.End(false); | ||
176 | } | ||
177 | |||
178 | private static IntPtr[] Begin(string transactionName, TransactionAttributes attributes) | ||
179 | { | ||
180 | int hTransaction; | ||
181 | IntPtr hChangeOfOwnerEvent; | ||
182 | uint ret = NativeMethods.MsiBeginTransaction(transactionName, (int) attributes, out hTransaction, out hChangeOfOwnerEvent); | ||
183 | if (ret != 0) | ||
184 | { | ||
185 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
186 | } | ||
187 | |||
188 | return new IntPtr[] { (IntPtr) hTransaction, hChangeOfOwnerEvent }; | ||
189 | } | ||
190 | |||
191 | [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] | ||
192 | private void End(bool commit) | ||
193 | { | ||
194 | uint ret = NativeMethods.MsiEndTransaction(commit ? 1 : 0); | ||
195 | if (ret != 0) | ||
196 | { | ||
197 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs new file mode 100644 index 00000000..3f75326e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs | |||
@@ -0,0 +1,46 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System.Diagnostics.CodeAnalysis; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Contains specific information about an error encountered by the <see cref="View.Validate"/>, | ||
9 | /// <see cref="View.ValidateNew"/>, or <see cref="View.ValidateFields"/> methods of the | ||
10 | /// <see cref="View"/> class. | ||
11 | /// </summary> | ||
12 | [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] | ||
13 | public struct ValidationErrorInfo | ||
14 | { | ||
15 | private ValidationError error; | ||
16 | private string column; | ||
17 | |||
18 | internal ValidationErrorInfo(ValidationError error, string column) | ||
19 | { | ||
20 | this.error = error; | ||
21 | this.column = column; | ||
22 | } | ||
23 | |||
24 | /// <summary> | ||
25 | /// Gets the type of validation error encountered. | ||
26 | /// </summary> | ||
27 | public ValidationError Error | ||
28 | { | ||
29 | get | ||
30 | { | ||
31 | return this.error; | ||
32 | } | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Gets the column containing the error, or null if the error applies to the whole row. | ||
37 | /// </summary> | ||
38 | public string Column | ||
39 | { | ||
40 | get | ||
41 | { | ||
42 | return this.column; | ||
43 | } | ||
44 | } | ||
45 | } | ||
46 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs new file mode 100644 index 00000000..21e8543e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs | |||
@@ -0,0 +1,625 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// A View represents a result set obtained when processing a query using the | ||
13 | /// <see cref="WixToolset.Dtf.WindowsInstaller.Database.OpenView"/> method of a | ||
14 | /// <see cref="Database"/>. Before any data can be transferred, | ||
15 | /// the query must be executed using the <see cref="Execute(Record)"/> method, passing to | ||
16 | /// it all replaceable parameters designated within the SQL query string. | ||
17 | /// </summary> | ||
18 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] | ||
19 | public class View : InstallerHandle, IEnumerable<Record> | ||
20 | { | ||
21 | private Database database; | ||
22 | private string sql; | ||
23 | private IList<TableInfo> tables; | ||
24 | private ColumnCollection columns; | ||
25 | |||
26 | internal View(IntPtr handle, string sql, Database database) | ||
27 | : base(handle, true) | ||
28 | { | ||
29 | this.sql = sql; | ||
30 | this.database = database; | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Gets the Database on which this View was opened. | ||
35 | /// </summary> | ||
36 | public Database Database | ||
37 | { | ||
38 | get { return this.database; } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets the SQL query string used to open this View. | ||
43 | /// </summary> | ||
44 | public string QueryString | ||
45 | { | ||
46 | get { return this.sql; } | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Gets the set of tables that were included in the SQL query for this View. | ||
51 | /// </summary> | ||
52 | public IList<TableInfo> Tables | ||
53 | { | ||
54 | get | ||
55 | { | ||
56 | if (this.tables == null) | ||
57 | { | ||
58 | if (this.sql == null) | ||
59 | { | ||
60 | return null; | ||
61 | } | ||
62 | |||
63 | // Parse the table names out of the SQL query string by looking | ||
64 | // for tokens that can come before or after the list of tables. | ||
65 | |||
66 | string parseSql = this.sql.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); | ||
67 | string upperSql = parseSql.ToUpper(CultureInfo.InvariantCulture); | ||
68 | |||
69 | string[] prefixes = new string[] { " FROM ", " INTO ", " TABLE " }; | ||
70 | string[] suffixes = new string[] { " WHERE ", " ORDER ", " SET ", " (", " ADD " }; | ||
71 | |||
72 | int index; | ||
73 | |||
74 | for (int i = 0; i < prefixes.Length; i++) | ||
75 | { | ||
76 | if ((index = upperSql.IndexOf(prefixes[i], StringComparison.Ordinal)) > 0) | ||
77 | { | ||
78 | parseSql = parseSql.Substring(index + prefixes[i].Length); | ||
79 | upperSql = upperSql.Substring(index + prefixes[i].Length); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | if (upperSql.StartsWith("UPDATE ", StringComparison.Ordinal)) | ||
84 | { | ||
85 | parseSql = parseSql.Substring(7); | ||
86 | upperSql = upperSql.Substring(7); | ||
87 | } | ||
88 | |||
89 | for (int i = 0; i < suffixes.Length; i++) | ||
90 | { | ||
91 | if ((index = upperSql.IndexOf(suffixes[i], StringComparison.Ordinal)) > 0) | ||
92 | { | ||
93 | parseSql = parseSql.Substring(0, index); | ||
94 | upperSql = upperSql.Substring(0, index); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if (upperSql.EndsWith(" HOLD", StringComparison.Ordinal) || | ||
99 | upperSql.EndsWith(" FREE", StringComparison.Ordinal)) | ||
100 | { | ||
101 | parseSql = parseSql.Substring(0, parseSql.Length - 5); | ||
102 | upperSql = upperSql.Substring(0, upperSql.Length - 5); | ||
103 | } | ||
104 | |||
105 | // At this point we should be left with a comma-separated list of table names, | ||
106 | // optionally quoted with grave accent marks (`). | ||
107 | |||
108 | string[] tableNames = parseSql.Split(','); | ||
109 | IList<TableInfo> tableList = new List<TableInfo>(tableNames.Length); | ||
110 | for (int i = 0; i < tableNames.Length; i++) | ||
111 | { | ||
112 | string tableName = tableNames[i].Trim(' ', '`'); | ||
113 | tableList.Add(new TableInfo(this.database, tableName)); | ||
114 | } | ||
115 | this.tables = tableList; | ||
116 | } | ||
117 | return new List<TableInfo>(this.tables); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Gets the set of columns that were included in the query for this View, | ||
123 | /// or null if this view is not a SELECT query. | ||
124 | /// </summary> | ||
125 | /// <exception cref="InstallerException">the View is not in an active state</exception> | ||
126 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
127 | /// <remarks><p> | ||
128 | /// Win32 MSI API: | ||
129 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgetcolumninfo.asp">MsiViewGetColumnInfo</a> | ||
130 | /// </p></remarks> | ||
131 | public ColumnCollection Columns | ||
132 | { | ||
133 | get | ||
134 | { | ||
135 | if (this.columns == null) | ||
136 | { | ||
137 | this.columns = new ColumnCollection(this); | ||
138 | } | ||
139 | return this.columns; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /// <summary> | ||
144 | /// Executes a SQL View query and supplies any required parameters. The query uses the | ||
145 | /// question mark token to represent parameters as described in SQL Syntax. The values of | ||
146 | /// these parameters are passed in as the corresponding fields of a parameter record. | ||
147 | /// </summary> | ||
148 | /// <param name="executeParams">Optional Record that supplies the parameters. This | ||
149 | /// Record contains values to replace the parameter tokens in the SQL query.</param> | ||
150 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
151 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
152 | /// <remarks><p> | ||
153 | /// Win32 MSI API: | ||
154 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a> | ||
155 | /// </p></remarks> | ||
156 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Params"), SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
157 | public void Execute(Record executeParams) | ||
158 | { | ||
159 | uint ret = RemotableNativeMethods.MsiViewExecute( | ||
160 | (int) this.Handle, | ||
161 | (executeParams != null ? (int) executeParams.Handle : 0)); | ||
162 | if (ret == (uint) NativeMethods.Error.BAD_QUERY_SYNTAX) | ||
163 | { | ||
164 | throw new BadQuerySyntaxException(this.sql); | ||
165 | } | ||
166 | else if (ret != 0) | ||
167 | { | ||
168 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | /// <summary> | ||
173 | /// Executes a SQL View query. | ||
174 | /// </summary> | ||
175 | /// <exception cref="InstallerException">the View could not be executed</exception> | ||
176 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
177 | /// <remarks><p> | ||
178 | /// Win32 MSI API: | ||
179 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a> | ||
180 | /// </p></remarks> | ||
181 | public void Execute() { this.Execute(null); } | ||
182 | |||
183 | /// <summary> | ||
184 | /// Fetches the next sequential record from the view, or null if there are no more records. | ||
185 | /// </summary> | ||
186 | /// <exception cref="InstallerException">the View is not in an active state</exception> | ||
187 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
188 | /// <remarks><p> | ||
189 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
190 | /// It is best that the handle be closed manually as soon as it is no longer | ||
191 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
192 | /// </p><p> | ||
193 | /// Win32 MSI API: | ||
194 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
195 | /// </p></remarks> | ||
196 | public Record Fetch() | ||
197 | { | ||
198 | int recordHandle; | ||
199 | uint ret = RemotableNativeMethods.MsiViewFetch((int) this.Handle, out recordHandle); | ||
200 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
201 | { | ||
202 | return null; | ||
203 | } | ||
204 | else if (ret != 0) | ||
205 | { | ||
206 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
207 | } | ||
208 | |||
209 | Record r = new Record((IntPtr) recordHandle, true, this); | ||
210 | r.IsFormatStringInvalid = true; | ||
211 | return r; | ||
212 | } | ||
213 | |||
214 | /// <summary> | ||
215 | /// Updates a fetched Record. | ||
216 | /// </summary> | ||
217 | /// <param name="mode">specifies the modify mode</param> | ||
218 | /// <param name="record">the Record to modify</param> | ||
219 | /// <exception cref="InstallerException">the modification failed, | ||
220 | /// or a validation was requested and the data did not pass</exception> | ||
221 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
222 | /// <remarks><p> | ||
223 | /// You can update or delete a record immediately after inserting, or seeking provided you | ||
224 | /// have NOT modified the 0th field of the inserted or sought record. | ||
225 | /// </p><p> | ||
226 | /// To execute any SQL statement, a View must be created. However, a View that does not | ||
227 | /// create a result set, such as CREATE TABLE, or INSERT INTO, cannot be used with any of | ||
228 | /// the Modify methods to update tables though the view. | ||
229 | /// </p><p> | ||
230 | /// You cannot fetch a record containing binary data from one database and then use | ||
231 | /// that record to insert the data into another database. To move binary data from one database | ||
232 | /// to another, you should export the data to a file and then import it into the new database | ||
233 | /// using a query and the <see cref="Record.SetStream(int,string)"/>. This ensures that each database has | ||
234 | /// its own copy of the binary data. | ||
235 | /// </p><p> | ||
236 | /// Note that custom actions can only add, modify, or remove temporary rows, columns, | ||
237 | /// or tables from a database. Custom actions cannot modify persistent data in a database, | ||
238 | /// such as data that is a part of the database stored on disk. | ||
239 | /// </p><p> | ||
240 | /// Win32 MSI API: | ||
241 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
242 | /// </p></remarks> | ||
243 | /// <seealso cref="Refresh"/> | ||
244 | /// <seealso cref="Insert"/> | ||
245 | /// <seealso cref="Update"/> | ||
246 | /// <seealso cref="Assign"/> | ||
247 | /// <seealso cref="Replace"/> | ||
248 | /// <seealso cref="Delete"/> | ||
249 | /// <seealso cref="InsertTemporary"/> | ||
250 | /// <seealso cref="Seek"/> | ||
251 | /// <seealso cref="Merge"/> | ||
252 | /// <seealso cref="Validate"/> | ||
253 | /// <seealso cref="ValidateNew"/> | ||
254 | /// <seealso cref="ValidateFields"/> | ||
255 | /// <seealso cref="ValidateDelete"/> | ||
256 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
257 | public void Modify(ViewModifyMode mode, Record record) | ||
258 | { | ||
259 | if (record == null) | ||
260 | { | ||
261 | throw new ArgumentNullException("record"); | ||
262 | } | ||
263 | |||
264 | uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle); | ||
265 | if (mode == ViewModifyMode.Insert || mode == ViewModifyMode.InsertTemporary) | ||
266 | { | ||
267 | record.IsFormatStringInvalid = true; | ||
268 | } | ||
269 | if (ret != 0) | ||
270 | { | ||
271 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | /// <summary> | ||
276 | /// Refreshes the data in a Record. | ||
277 | /// </summary> | ||
278 | /// <param name="record">the Record to be refreshed</param> | ||
279 | /// <exception cref="InstallerException">the refresh failed</exception> | ||
280 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
281 | /// <remarks><p> | ||
282 | /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails with | ||
283 | /// a deleted Record. Works only with read-write Records. | ||
284 | /// </p><p> | ||
285 | /// See <see cref="Modify"/> for more remarks. | ||
286 | /// </p><p> | ||
287 | /// Win32 MSI API: | ||
288 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
289 | /// </p></remarks> | ||
290 | public void Refresh(Record record) { this.Modify(ViewModifyMode.Refresh, record); } | ||
291 | |||
292 | /// <summary> | ||
293 | /// Inserts a Record into the view. | ||
294 | /// </summary> | ||
295 | /// <param name="record">the Record to be inserted</param> | ||
296 | /// <exception cref="InstallerException">the insertion failed</exception> | ||
297 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
298 | /// <remarks><p> | ||
299 | /// Fails if a row with the same primary keys exists. Fails with a read-only database. | ||
300 | /// This method cannot be used with a View containing joins. | ||
301 | /// </p><p> | ||
302 | /// See <see cref="Modify"/> for more remarks. | ||
303 | /// </p><p> | ||
304 | /// Win32 MSI API: | ||
305 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
306 | /// </p></remarks> | ||
307 | public void Insert(Record record) { this.Modify(ViewModifyMode.Insert, record); } | ||
308 | |||
309 | /// <summary> | ||
310 | /// Updates the View with new data from the Record. | ||
311 | /// </summary> | ||
312 | /// <param name="record">the new data</param> | ||
313 | /// <exception cref="InstallerException">the update failed</exception> | ||
314 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
315 | /// <remarks><p> | ||
316 | /// Only non-primary keys can be updated. The Record must have been obtained by calling | ||
317 | /// <see cref="Fetch"/>. Fails with a deleted Record. Works only with read-write Records. | ||
318 | /// </p><p> | ||
319 | /// See <see cref="Modify"/> for more remarks. | ||
320 | /// </p><p> | ||
321 | /// Win32 MSI API: | ||
322 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
323 | /// </p></remarks> | ||
324 | public void Update(Record record) { this.Modify(ViewModifyMode.Update, record); } | ||
325 | |||
326 | /// <summary> | ||
327 | /// Updates or inserts a Record into the View. | ||
328 | /// </summary> | ||
329 | /// <param name="record">the Record to be assigned</param> | ||
330 | /// <exception cref="InstallerException">the assignment failed</exception> | ||
331 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
332 | /// <remarks><p> | ||
333 | /// Updates record if the primary keys match an existing row and inserts if they do not match. | ||
334 | /// Fails with a read-only database. This method cannot be used with a View containing joins. | ||
335 | /// </p><p> | ||
336 | /// See <see cref="Modify"/> for more remarks. | ||
337 | /// </p><p> | ||
338 | /// Win32 MSI API: | ||
339 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
340 | /// </p></remarks> | ||
341 | public void Assign(Record record) { this.Modify(ViewModifyMode.Assign, record); } | ||
342 | |||
343 | /// <summary> | ||
344 | /// Updates or deletes and inserts a Record into the View. | ||
345 | /// </summary> | ||
346 | /// <param name="record">the Record to be replaced</param> | ||
347 | /// <exception cref="InstallerException">the replacement failed</exception> | ||
348 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
349 | /// <remarks><p> | ||
350 | /// The Record must have been obtained by calling <see cref="Fetch"/>. Updates record if the | ||
351 | /// primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. | ||
352 | /// Fails with a read-only database. This method cannot be used with a View containing joins. | ||
353 | /// </p><p> | ||
354 | /// See <see cref="Modify"/> for more remarks. | ||
355 | /// </p><p> | ||
356 | /// Win32 MSI API: | ||
357 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
358 | /// </p></remarks> | ||
359 | public void Replace(Record record) { this.Modify(ViewModifyMode.Replace, record); } | ||
360 | |||
361 | /// <summary> | ||
362 | /// Deletes a Record from the View. | ||
363 | /// </summary> | ||
364 | /// <param name="record">the Record to be deleted</param> | ||
365 | /// <exception cref="InstallerException">the deletion failed</exception> | ||
366 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
367 | /// <remarks><p> | ||
368 | /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails if the row has been | ||
369 | /// deleted. Works only with read-write records. This method cannot be used with a View containing joins. | ||
370 | /// </p><p> | ||
371 | /// See <see cref="Modify"/> for more remarks. | ||
372 | /// </p><p> | ||
373 | /// Win32 MSI API: | ||
374 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
375 | /// </p></remarks> | ||
376 | public void Delete(Record record) { this.Modify(ViewModifyMode.Delete, record); } | ||
377 | |||
378 | /// <summary> | ||
379 | /// Inserts a Record into the View. The inserted data is not persistent. | ||
380 | /// </summary> | ||
381 | /// <param name="record">the Record to be inserted</param> | ||
382 | /// <exception cref="InstallerException">the insertion failed</exception> | ||
383 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
384 | /// <remarks><p> | ||
385 | /// Fails if a row with the same primary key exists. Works only with read-write records. | ||
386 | /// This method cannot be used with a View containing joins. | ||
387 | /// </p><p> | ||
388 | /// See <see cref="Modify"/> for more remarks. | ||
389 | /// </p><p> | ||
390 | /// Win32 MSI API: | ||
391 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
392 | /// </p></remarks> | ||
393 | public void InsertTemporary(Record record) { this.Modify(ViewModifyMode.InsertTemporary, record); } | ||
394 | |||
395 | /// <summary> | ||
396 | /// Refreshes the information in the supplied record without changing the position | ||
397 | /// in the result set and without affecting subsequent fetch operations. | ||
398 | /// </summary> | ||
399 | /// <param name="record">the Record to be filled with the result of the seek</param> | ||
400 | /// <exception cref="InstallerException">the seek failed</exception> | ||
401 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
402 | /// <remarks><p> | ||
403 | /// After seeking, the Record may then be used for subsequent Update, Delete, and Refresh | ||
404 | /// operations. All primary key columns of the table must be in the query and the Record must | ||
405 | /// have at least as many fields as the query. Seek cannot be used with multi-table queries. | ||
406 | /// This method cannot be used with a View containing joins. | ||
407 | /// </p><p> | ||
408 | /// See <see cref="Modify"/> for more remarks. | ||
409 | /// </p><p> | ||
410 | /// Win32 MSI API: | ||
411 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
412 | /// </p></remarks> | ||
413 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
414 | public bool Seek(Record record) | ||
415 | { | ||
416 | if (record == null) | ||
417 | { | ||
418 | throw new ArgumentNullException("record"); | ||
419 | } | ||
420 | |||
421 | uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Seek, (int) record.Handle); | ||
422 | record.IsFormatStringInvalid = true; | ||
423 | if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED) | ||
424 | { | ||
425 | return false; | ||
426 | } | ||
427 | else if (ret != 0) | ||
428 | { | ||
429 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
430 | } | ||
431 | |||
432 | return true; | ||
433 | } | ||
434 | |||
435 | /// <summary> | ||
436 | /// Inserts or validates a record. | ||
437 | /// </summary> | ||
438 | /// <param name="record">the Record to be merged</param> | ||
439 | /// <returns>true if the record was inserted or validated, false if there is an existing | ||
440 | /// record with the same primary keys that is not identical</returns> | ||
441 | /// <exception cref="InstallerException">the merge failed (for a reason other than invalid data)</exception> | ||
442 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
443 | /// <remarks><p> | ||
444 | /// Works only with read-write records. This method cannot be used with a | ||
445 | /// View containing joins. | ||
446 | /// </p><p> | ||
447 | /// See <see cref="Modify"/> for more remarks. | ||
448 | /// </p><p> | ||
449 | /// Win32 MSI API: | ||
450 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a> | ||
451 | /// </p></remarks> | ||
452 | [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] | ||
453 | public bool Merge(Record record) | ||
454 | { | ||
455 | if (record == null) | ||
456 | { | ||
457 | throw new ArgumentNullException("record"); | ||
458 | } | ||
459 | |||
460 | uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Merge, (int) record.Handle); | ||
461 | if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED) | ||
462 | { | ||
463 | return false; | ||
464 | } | ||
465 | else if (ret != 0) | ||
466 | { | ||
467 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
468 | } | ||
469 | return true; | ||
470 | } | ||
471 | |||
472 | /// <summary> | ||
473 | /// Validates a record, returning information about any errors. | ||
474 | /// </summary> | ||
475 | /// <param name="record">the Record to be validated</param> | ||
476 | /// <returns>null if the record was validated; if there is an existing record with | ||
477 | /// the same primary keys that has conflicting data then error information is returned</returns> | ||
478 | /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception> | ||
479 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
480 | /// <remarks><p> | ||
481 | /// The Record must have been obtained by calling <see cref="Fetch"/>. | ||
482 | /// Works with read-write and read-only records. This method cannot be used | ||
483 | /// with a View containing joins. | ||
484 | /// </p><p> | ||
485 | /// See <see cref="Modify"/> for more remarks. | ||
486 | /// </p><p> | ||
487 | /// Win32 MSI APIs: | ||
488 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>, | ||
489 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a> | ||
490 | /// </p></remarks> | ||
491 | public ICollection<ValidationErrorInfo> Validate(Record record) { return this.InternalValidate(ViewModifyMode.Validate, record); } | ||
492 | |||
493 | /// <summary> | ||
494 | /// Validates a new record, returning information about any errors. | ||
495 | /// </summary> | ||
496 | /// <param name="record">the Record to be validated</param> | ||
497 | /// <returns>null if the record was validated; if there is an existing | ||
498 | /// record with the same primary keys then error information is returned</returns> | ||
499 | /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception> | ||
500 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
501 | /// <remarks><p> | ||
502 | /// Checks for duplicate keys. The Record must have been obtained by | ||
503 | /// calling <see cref="Fetch"/>. Works with read-write and read-only records. | ||
504 | /// This method cannot be used with a View containing joins. | ||
505 | /// </p><p> | ||
506 | /// See <see cref="Modify"/> for more remarks. | ||
507 | /// </p><p> | ||
508 | /// Win32 MSI APIs: | ||
509 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>, | ||
510 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a> | ||
511 | /// </p></remarks> | ||
512 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] | ||
513 | public ICollection<ValidationErrorInfo> ValidateNew(Record record) { return this.InternalValidate(ViewModifyMode.ValidateNew, record); } | ||
514 | |||
515 | /// <summary> | ||
516 | /// Validates fields of a fetched or new record, returning information about any errors. | ||
517 | /// Can validate one or more fields of an incomplete record. | ||
518 | /// </summary> | ||
519 | /// <param name="record">the Record to be validated</param> | ||
520 | /// <returns>null if the record was validated; if there is an existing record with | ||
521 | /// the same primary keys that has conflicting data then error information is returned</returns> | ||
522 | /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception> | ||
523 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
524 | /// <remarks><p> | ||
525 | /// Works with read-write and read-only records. This method cannot be used with | ||
526 | /// a View containing joins. | ||
527 | /// </p><p> | ||
528 | /// See <see cref="Modify"/> for more remarks. | ||
529 | /// </p><p> | ||
530 | /// Win32 MSI APIs: | ||
531 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>, | ||
532 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a> | ||
533 | /// </p></remarks> | ||
534 | public ICollection<ValidationErrorInfo> ValidateFields(Record record) { return this.InternalValidate(ViewModifyMode.ValidateField, record); } | ||
535 | |||
536 | /// <summary> | ||
537 | /// Validates a record that will be deleted later, returning information about any errors. | ||
538 | /// </summary> | ||
539 | /// <param name="record">the Record to be validated</param> | ||
540 | /// <returns>null if the record is safe to delete; if another row refers to | ||
541 | /// the primary keys of this row then error information is returned</returns> | ||
542 | /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception> | ||
543 | /// <exception cref="InvalidHandleException">the View handle is invalid</exception> | ||
544 | /// <remarks><p> | ||
545 | /// Validation does not check for the existence of the primary keys of this row in properties | ||
546 | /// or strings. Does not check if a column is a foreign key to multiple tables. Works with | ||
547 | /// read-write and read-only records. This method cannot be used with a View containing joins. | ||
548 | /// </p><p> | ||
549 | /// See <see cref="Modify"/> for more remarks. | ||
550 | /// </p><p> | ||
551 | /// Win32 MSI APIs: | ||
552 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>, | ||
553 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a> | ||
554 | /// </p></remarks> | ||
555 | public ICollection<ValidationErrorInfo> ValidateDelete(Record record) { return this.InternalValidate(ViewModifyMode.ValidateDelete, record); } | ||
556 | |||
557 | /// <summary> | ||
558 | /// Enumerates over the Records retrieved by the View. | ||
559 | /// </summary> | ||
560 | /// <returns>An enumerator of Record objects.</returns> | ||
561 | /// <exception cref="InstallerException">The View was not <see cref="Execute(Record)"/>d before attempting the enumeration.</exception> | ||
562 | /// <remarks><p> | ||
563 | /// Each Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
564 | /// It is best that the handle be closed manually as soon as it is no longer | ||
565 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
566 | /// However, note that it is not necessary to complete the enumeration just | ||
567 | /// for the purpose of closing handles, because Records are fetched lazily | ||
568 | /// on each step of the enumeration. | ||
569 | /// </p><p> | ||
570 | /// Win32 MSI API: | ||
571 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a> | ||
572 | /// </p></remarks> | ||
573 | public IEnumerator<Record> GetEnumerator() | ||
574 | { | ||
575 | Record rec; | ||
576 | while ((rec = this.Fetch()) != null) | ||
577 | { | ||
578 | yield return rec; | ||
579 | } | ||
580 | } | ||
581 | |||
582 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
583 | { | ||
584 | return this.GetEnumerator(); | ||
585 | } | ||
586 | |||
587 | private ICollection<ValidationErrorInfo> InternalValidate(ViewModifyMode mode, Record record) | ||
588 | { | ||
589 | uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle); | ||
590 | if (ret == (uint) NativeMethods.Error.INVALID_DATA) | ||
591 | { | ||
592 | ICollection<ValidationErrorInfo> errorInfo = new List<ValidationErrorInfo>(); | ||
593 | while (true) | ||
594 | { | ||
595 | uint bufSize = 40; | ||
596 | StringBuilder column = new StringBuilder("", (int) bufSize); | ||
597 | int error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize); | ||
598 | if (error == -2 /*MSIDBERROR_MOREDATA*/) | ||
599 | { | ||
600 | column.Capacity = (int) ++bufSize; | ||
601 | error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize); | ||
602 | } | ||
603 | |||
604 | if (error == -3 /*MSIDBERROR_INVALIDARG*/) | ||
605 | { | ||
606 | throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.INVALID_PARAMETER); | ||
607 | } | ||
608 | else if (error == 0 /*MSIDBERROR_NOERROR*/) | ||
609 | { | ||
610 | break; | ||
611 | } | ||
612 | |||
613 | errorInfo.Add(new ValidationErrorInfo((ValidationError) error, column.ToString())); | ||
614 | } | ||
615 | |||
616 | return errorInfo; | ||
617 | } | ||
618 | else if (ret != 0) | ||
619 | { | ||
620 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
621 | } | ||
622 | return null; | ||
623 | } | ||
624 | } | ||
625 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd b/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd new file mode 100644 index 00000000..01b68153 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd | |||
@@ -0,0 +1,943 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <ClassDiagram MajorVersion="1" MinorVersion="1"> | ||
3 | <Comment CommentText="Database classes"> | ||
4 | <Position X="3.433" Y="7.717" Height="0.26" Width="1.159" /> | ||
5 | </Comment> | ||
6 | <Comment CommentText="Custom action classes"> | ||
7 | <Position X="5.791" Y="1.774" Height="0.312" Width="1.465" /> | ||
8 | </Comment> | ||
9 | <Comment CommentText="Inventory classes"> | ||
10 | <Position X="12.323" Y="2.386" Height="0.292" Width="1.183" /> | ||
11 | </Comment> | ||
12 | <Comment CommentText="Exceptions"> | ||
13 | <Position X="11.75" Y="9.25" Height="0.285" Width="0.798" /> | ||
14 | </Comment> | ||
15 | <Comment CommentText="Callback delegates"> | ||
16 | <Position X="17.25" Y="8" Height="0.281" Width="1.25" /> | ||
17 | </Comment> | ||
18 | <Comment CommentText="API enums"> | ||
19 | <Position X="21.992" Y="0.5" Height="0.283" Width="2.008" /> | ||
20 | </Comment> | ||
21 | <Comment CommentText="Database column enums"> | ||
22 | <Position X="24.25" Y="0.5" Height="0.26" Width="1.983" /> | ||
23 | </Comment> | ||
24 | <Class Name="WixToolset.Dtf.WindowsInstaller.Database"> | ||
25 | <Position X="0.5" Y="5" Width="2.25" /> | ||
26 | <Members> | ||
27 | <Method Name="Database" Hidden="true" /> | ||
28 | <Field Name="deleteOnClose" Hidden="true" /> | ||
29 | <Method Name="Dispose" Hidden="true" /> | ||
30 | <Method Name="ExecutePropertyQuery" Hidden="true" /> | ||
31 | <Field Name="filePath" Hidden="true" /> | ||
32 | <Method Name="Open" Hidden="true" /> | ||
33 | <Field Name="openMode" Hidden="true" /> | ||
34 | <Field Name="summaryInfo" Hidden="true" /> | ||
35 | <Field Name="tables" Hidden="true" /> | ||
36 | </Members> | ||
37 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true"> | ||
38 | <Path> | ||
39 | <Point X="3.312" Y="2.119" /> | ||
40 | <Point X="3.312" Y="2.689" /> | ||
41 | <Point X="2.699" Y="2.689" /> | ||
42 | <Point X="2.699" Y="5" /> | ||
43 | </Path> | ||
44 | </InheritanceLine> | ||
45 | <AssociationLine Name="Tables" Type="WixToolset.Dtf.WindowsInstaller.TableInfo" FixedFromPoint="true" FixedToPoint="true"> | ||
46 | <Path> | ||
47 | <Point X="2.75" Y="8.125" /> | ||
48 | <Point X="3.75" Y="8.125" /> | ||
49 | <Point X="3.75" Y="8.5" /> | ||
50 | </Path> | ||
51 | <MemberNameLabel ManuallyPlaced="true"> | ||
52 | <Position X="0.293" Y="0.152" /> | ||
53 | </MemberNameLabel> | ||
54 | </AssociationLine> | ||
55 | <AssociationLine Name="SummaryInfo" Type="WixToolset.Dtf.WindowsInstaller.SummaryInfo" FixedFromPoint="true"> | ||
56 | <Path> | ||
57 | <Point X="1.318" Y="5" /> | ||
58 | <Point X="1.318" Y="4.535" /> | ||
59 | </Path> | ||
60 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
61 | <Position X="-1.155" Y="0.279" Height="0.16" Width="1.095" /> | ||
62 | </MemberNameLabel> | ||
63 | </AssociationLine> | ||
64 | <TypeIdentifier> | ||
65 | <HashCode>QABACUgAACAAIAVUIAgQREACAAIAk0ABAIhmoEAAAAA=</HashCode> | ||
66 | <FileName>Database.cs</FileName> | ||
67 | </TypeIdentifier> | ||
68 | <ShowAsAssociation> | ||
69 | <Property Name="SummaryInfo" /> | ||
70 | </ShowAsAssociation> | ||
71 | <ShowAsCollectionAssociation> | ||
72 | <Property Name="Tables" /> | ||
73 | </ShowAsCollectionAssociation> | ||
74 | </Class> | ||
75 | <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerHandle"> | ||
76 | <Position X="3" Y="0.5" Width="2" /> | ||
77 | <Members> | ||
78 | <Method Name="Dispose" Hidden="true" /> | ||
79 | <Method Name="Equals" Hidden="true" /> | ||
80 | <Method Name="GetHashCode" Hidden="true" /> | ||
81 | <Field Name="handle" Hidden="true" /> | ||
82 | <Method Name="InstallerHandle" Hidden="true" /> | ||
83 | <Property Name="Sync" Hidden="true" /> | ||
84 | </Members> | ||
85 | <TypeIdentifier> | ||
86 | <HashCode>AAAAAAAAACAAAACAgAAAAAAAAAAAAMIAAAACAACAAAA=</HashCode> | ||
87 | <FileName>Handle.cs</FileName> | ||
88 | </TypeIdentifier> | ||
89 | <Lollipop Position="0.2" /> | ||
90 | </Class> | ||
91 | <Class Name="WixToolset.Dtf.WindowsInstaller.Installer"> | ||
92 | <Position X="19.25" Y="0.5" Width="2.5" /> | ||
93 | <Members> | ||
94 | <Method Name="CheckInstallResult" Hidden="true" /> | ||
95 | <Field Name="errorResources" Hidden="true" /> | ||
96 | <Property Name="ErrorResources" Hidden="true" /> | ||
97 | <Field Name="externalUIHandlers" Hidden="true" /> | ||
98 | <Method Name="GetMessageFromModule" Hidden="true" /> | ||
99 | <Method Name="GetPatchStringDataType" Hidden="true" /> | ||
100 | <Field Name="rebootInitiated" Hidden="true" /> | ||
101 | <Field Name="rebootRequired" Hidden="true" /> | ||
102 | </Members> | ||
103 | <TypeIdentifier> | ||
104 | <HashCode>IAQBiIgAgAKAAAAELEAAPBgJAUAAYAAAAOQDagAgAQE=</HashCode> | ||
105 | <FileName>ExternalUIHandler.cs</FileName> | ||
106 | </TypeIdentifier> | ||
107 | </Class> | ||
108 | <Class Name="WixToolset.Dtf.WindowsInstaller.Record"> | ||
109 | <Position X="5.25" Y="3" Width="2" /> | ||
110 | <Members> | ||
111 | <Method Name="CheckRange" Hidden="true" /> | ||
112 | <Method Name="ExtractSubStorage" Hidden="true" /> | ||
113 | <Method Name="FindColumn" Hidden="true" /> | ||
114 | <Method Name="Record" Hidden="true" /> | ||
115 | <Field Name="view" Hidden="true" /> | ||
116 | </Members> | ||
117 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true"> | ||
118 | <Path> | ||
119 | <Point X="3.688" Y="2.119" /> | ||
120 | <Point X="3.688" Y="2.672" /> | ||
121 | <Point X="5.4" Y="2.672" /> | ||
122 | <Point X="5.4" Y="3" /> | ||
123 | </Path> | ||
124 | </InheritanceLine> | ||
125 | <TypeIdentifier> | ||
126 | <HashCode>AIAAAAAAAAAAAIAEiEIAJAAAAQgIEIAIIQCAAABIAAA=</HashCode> | ||
127 | <FileName>Record.cs</FileName> | ||
128 | </TypeIdentifier> | ||
129 | </Class> | ||
130 | <Class Name="WixToolset.Dtf.WindowsInstaller.Session"> | ||
131 | <Position X="7.5" Y="0.5" Width="2.25" /> | ||
132 | <Members> | ||
133 | <Field Name="database" Hidden="true" /> | ||
134 | <Method Name="Dispose" Hidden="true" /> | ||
135 | <Method Name="IFormatProvider.GetFormat" Hidden="true" /> | ||
136 | <Method Name="Session" Hidden="true" /> | ||
137 | </Members> | ||
138 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true"> | ||
139 | <Path> | ||
140 | <Point X="3.875" Y="2.119" /> | ||
141 | <Point X="3.875" Y="2.395" /> | ||
142 | <Point X="7.5" Y="2.395" /> | ||
143 | </Path> | ||
144 | </InheritanceLine> | ||
145 | <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true"> | ||
146 | <Path> | ||
147 | <Point X="7.562" Y="4.696" /> | ||
148 | <Point X="7.562" Y="10.375" /> | ||
149 | <Point X="2.75" Y="10.375" /> | ||
150 | </Path> | ||
151 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
152 | <Position X="4.869" Y="5.46" Height="0.16" Width="0.806" /> | ||
153 | </MemberNameLabel> | ||
154 | </AssociationLine> | ||
155 | <AssociationLine Name="Components" Type="WixToolset.Dtf.WindowsInstaller.ComponentInfo" FixedFromPoint="true"> | ||
156 | <Path> | ||
157 | <Point X="8.562" Y="4.696" /> | ||
158 | <Point X="8.562" Y="5.5" /> | ||
159 | </Path> | ||
160 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
161 | <Position X="-1.021" Y="0.317" Height="0.16" Width="0.986" /> | ||
162 | </MemberNameLabel> | ||
163 | </AssociationLine> | ||
164 | <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInfo"> | ||
165 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
166 | <Position X="0.016" Y="2.568" Height="0.16" Width="0.785" /> | ||
167 | </MemberNameLabel> | ||
168 | </AssociationLine> | ||
169 | <TypeIdentifier> | ||
170 | <HashCode>AAAAEAAAKGAAAoDADIBBAAAAAAgIEABBEIAAIAAACAA=</HashCode> | ||
171 | <FileName>Session.cs</FileName> | ||
172 | </TypeIdentifier> | ||
173 | <ShowAsAssociation> | ||
174 | <Property Name="Database" /> | ||
175 | </ShowAsAssociation> | ||
176 | <ShowAsCollectionAssociation> | ||
177 | <Property Name="Components" /> | ||
178 | <Property Name="Features" /> | ||
179 | </ShowAsCollectionAssociation> | ||
180 | <Lollipop Position="0.2" /> | ||
181 | </Class> | ||
182 | <Class Name="WixToolset.Dtf.WindowsInstaller.SummaryInfo"> | ||
183 | <Position X="0.5" Y="0.5" Width="2" /> | ||
184 | <Members> | ||
185 | <Field Name="MAX_PROPERTIES" Hidden="true" /> | ||
186 | <Method Name="OpenSummaryInfo" Hidden="true" /> | ||
187 | <Method Name="SummaryInfo" Hidden="true" /> | ||
188 | <Property Name="this" Hidden="true" /> | ||
189 | </Members> | ||
190 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true"> | ||
191 | <Path> | ||
192 | <Point X="3.125" Y="2.119" /> | ||
193 | <Point X="3.125" Y="2.404" /> | ||
194 | <Point X="2.5" Y="2.404" /> | ||
195 | </Path> | ||
196 | </InheritanceLine> | ||
197 | <TypeIdentifier> | ||
198 | <HashCode>AQAAAAAAAEAAAEAAjMgAAJIAAAEAAAAAAAAEhAAAoCI=</HashCode> | ||
199 | <FileName>SummaryInfo.cs</FileName> | ||
200 | </TypeIdentifier> | ||
201 | </Class> | ||
202 | <Class Name="WixToolset.Dtf.WindowsInstaller.View"> | ||
203 | <Position X="3" Y="3" Width="2" /> | ||
204 | <Members> | ||
205 | <Field Name="columns" Hidden="true" /> | ||
206 | <Field Name="database" Hidden="true" /> | ||
207 | <Method Name="InternalValidate" Hidden="true" /> | ||
208 | <Field Name="sql" Hidden="true" /> | ||
209 | <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" /> | ||
210 | <Field Name="tables" Hidden="true" /> | ||
211 | <Method Name="View" Hidden="true" /> | ||
212 | </Members> | ||
213 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true" FixedToPoint="true"> | ||
214 | <Path> | ||
215 | <Point X="3.5" Y="2.119" /> | ||
216 | <Point X="3.5" Y="3" /> | ||
217 | </Path> | ||
218 | </InheritanceLine> | ||
219 | <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true"> | ||
220 | <Path> | ||
221 | <Point X="3.75" Y="7.196" /> | ||
222 | <Point X="3.75" Y="7.562" /> | ||
223 | <Point X="2.75" Y="7.562" /> | ||
224 | </Path> | ||
225 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
226 | <Position X="0.168" Y="0.16" Height="0.16" Width="0.831" /> | ||
227 | </MemberNameLabel> | ||
228 | </AssociationLine> | ||
229 | <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true"> | ||
230 | <Path> | ||
231 | <Point X="4.25" Y="7.196" /> | ||
232 | <Point X="4.25" Y="7.562" /> | ||
233 | <Point X="5.25" Y="7.562" /> | ||
234 | </Path> | ||
235 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
236 | <Position X="0.168" Y="0.152" Height="0.16" Width="0.787" /> | ||
237 | </MemberNameLabel> | ||
238 | </AssociationLine> | ||
239 | <TypeIdentifier> | ||
240 | <HashCode>ICQAIEQAAEDgABRACQEAQECBAAEAAQAACAAAAAgAACA=</HashCode> | ||
241 | <FileName>View.cs</FileName> | ||
242 | </TypeIdentifier> | ||
243 | <ShowAsAssociation> | ||
244 | <Property Name="Database" /> | ||
245 | </ShowAsAssociation> | ||
246 | <ShowAsCollectionAssociation> | ||
247 | <Property Name="Columns" /> | ||
248 | </ShowAsCollectionAssociation> | ||
249 | <Lollipop Position="0.638" /> | ||
250 | </Class> | ||
251 | <Class Name="WixToolset.Dtf.WindowsInstaller.TableInfo"> | ||
252 | <Position X="3" Y="8.5" Width="2" /> | ||
253 | <Members> | ||
254 | <Field Name="columns" Hidden="true" /> | ||
255 | <Method Name="GetTablePrimaryKeys" Hidden="true" /> | ||
256 | <Field Name="name" Hidden="true" /> | ||
257 | <Field Name="primaryKeys" Hidden="true" /> | ||
258 | <Method Name="TableInfo" Hidden="true" /> | ||
259 | <Method Name="ToString" Hidden="true" /> | ||
260 | </Members> | ||
261 | <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true"> | ||
262 | <Path> | ||
263 | <Point X="4.25" Y="8.5" /> | ||
264 | <Point X="4.25" Y="8.125" /> | ||
265 | <Point X="5.25" Y="8.125" /> | ||
266 | </Path> | ||
267 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
268 | <Position X="0.143" Y="-0.338" Height="0.16" Width="0.787" /> | ||
269 | </MemberNameLabel> | ||
270 | </AssociationLine> | ||
271 | <TypeIdentifier> | ||
272 | <HashCode>AAAAAAAAAEAAAAAEACAAAAQAQAAEAAAASAAAAAgAAAA=</HashCode> | ||
273 | <FileName>TableInfo.cs</FileName> | ||
274 | </TypeIdentifier> | ||
275 | <ShowAsCollectionAssociation> | ||
276 | <Property Name="Columns" /> | ||
277 | </ShowAsCollectionAssociation> | ||
278 | </Class> | ||
279 | <Class Name="WixToolset.Dtf.WindowsInstaller.ColumnInfo"> | ||
280 | <Position X="5.25" Y="7.25" Width="2" /> | ||
281 | <Members> | ||
282 | <Method Name="ColumnInfo" Hidden="true" /> | ||
283 | <Field Name="isLocalizable" Hidden="true" /> | ||
284 | <Field Name="isRequired" Hidden="true" /> | ||
285 | <Field Name="isTemporary" Hidden="true" /> | ||
286 | <Field Name="name" Hidden="true" /> | ||
287 | <Field Name="size" Hidden="true" /> | ||
288 | <Method Name="ToString" Hidden="true" /> | ||
289 | <Field Name="type" Hidden="true" /> | ||
290 | </Members> | ||
291 | <TypeIdentifier> | ||
292 | <HashCode>AgAAAAAAAAAAgCAEEIAAABQgQAAFIAAAAQAAAAIAEAA=</HashCode> | ||
293 | <FileName>ColumnInfo.cs</FileName> | ||
294 | </TypeIdentifier> | ||
295 | </Class> | ||
296 | <Class Name="WixToolset.Dtf.WindowsInstaller.CustomActionAttribute"> | ||
297 | <Position X="5.25" Y="0.5" Width="2" /> | ||
298 | <Members> | ||
299 | <Method Name="CustomActionAttribute" Hidden="true" /> | ||
300 | <Field Name="name" Hidden="true" /> | ||
301 | </Members> | ||
302 | <TypeIdentifier> | ||
303 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAA=</HashCode> | ||
304 | <FileName>CustomActionAttribute.cs</FileName> | ||
305 | </TypeIdentifier> | ||
306 | </Class> | ||
307 | <Class Name="WixToolset.Dtf.WindowsInstaller.Installation"> | ||
308 | <Position X="12.5" Y="0.5" Width="2" /> | ||
309 | <Members> | ||
310 | <Field Name="context" Hidden="true" /> | ||
311 | <Method Name="Installation" Hidden="true" /> | ||
312 | <Field Name="installationCode" Hidden="true" /> | ||
313 | <Property Name="InstallationCode" Hidden="true" /> | ||
314 | <Property Name="InstallationType" Hidden="true" /> | ||
315 | <Field Name="sourceList" Hidden="true" /> | ||
316 | <Field Name="userSid" Hidden="true" /> | ||
317 | </Members> | ||
318 | <AssociationLine Name="SourceList" Type="WixToolset.Dtf.WindowsInstaller.SourceList" FixedFromPoint="true" FixedToPoint="true"> | ||
319 | <Path> | ||
320 | <Point X="13.75" Y="1.95" /> | ||
321 | <Point X="13.75" Y="2.298" /> | ||
322 | <Point X="14.75" Y="2.298" /> | ||
323 | </Path> | ||
324 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
325 | <Position X="0.076" Y="0.114" Height="0.16" Width="0.868" /> | ||
326 | </MemberNameLabel> | ||
327 | </AssociationLine> | ||
328 | <TypeIdentifier> | ||
329 | <HashCode>AAACIhAAAoAQAACACAAAAABAAAAAAAAAAAAEAAAAAAA=</HashCode> | ||
330 | <FileName>Installation.cs</FileName> | ||
331 | </TypeIdentifier> | ||
332 | <ShowAsAssociation> | ||
333 | <Property Name="SourceList" /> | ||
334 | </ShowAsAssociation> | ||
335 | </Class> | ||
336 | <Class Name="WixToolset.Dtf.WindowsInstaller.InstallationPart"> | ||
337 | <Position X="17" Y="3" Width="2" /> | ||
338 | <Members> | ||
339 | <Field Name="id" Hidden="true" /> | ||
340 | <Property Name="Id" Hidden="true" /> | ||
341 | <Method Name="InstallationPart" Hidden="true" /> | ||
342 | <Field Name="productCode" Hidden="true" /> | ||
343 | <Property Name="ProductCode" Hidden="true" /> | ||
344 | </Members> | ||
345 | <AssociationLine Name="Product" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true"> | ||
346 | <Path> | ||
347 | <Point X="17.611" Y="3.967" /> | ||
348 | <Point X="17.611" Y="4.519" /> | ||
349 | <Point X="14.76" Y="4.519" /> | ||
350 | <Point X="14.76" Y="6.438" /> | ||
351 | <Point X="12.25" Y="6.438" /> | ||
352 | </Path> | ||
353 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
354 | <Position X="4.65" Y="2.262" Height="0.16" Width="0.703" /> | ||
355 | </MemberNameLabel> | ||
356 | </AssociationLine> | ||
357 | <TypeIdentifier> | ||
358 | <HashCode>AAAiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAIgA=</HashCode> | ||
359 | <FileName>InstallationPart.cs</FileName> | ||
360 | </TypeIdentifier> | ||
361 | <ShowAsAssociation> | ||
362 | <Property Name="Product" /> | ||
363 | </ShowAsAssociation> | ||
364 | </Class> | ||
365 | <Class Name="WixToolset.Dtf.WindowsInstaller.ProductInstallation"> | ||
366 | <Position X="10" Y="0.5" Width="2.25" /> | ||
367 | <Members> | ||
368 | <Method Name="EnumFeatures" Hidden="true" /> | ||
369 | <Method Name="EnumProducts" Hidden="true" /> | ||
370 | <Method Name="EnumRelatedProducts" Hidden="true" /> | ||
371 | <Property Name="InstallationType" Hidden="true" /> | ||
372 | <Field Name="properties" Hidden="true" /> | ||
373 | <Property Name="State" Hidden="true" /> | ||
374 | </Members> | ||
375 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true"> | ||
376 | <Path> | ||
377 | <Point X="13.312" Y="1.95" /> | ||
378 | <Point X="13.312" Y="2.312" /> | ||
379 | <Point X="12.25" Y="2.312" /> | ||
380 | </Path> | ||
381 | </InheritanceLine> | ||
382 | <AssociationLine Name="AllProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true" FixedToPoint="true"> | ||
383 | <Path> | ||
384 | <Point X="10.5" Y="7.273" /> | ||
385 | <Point X="10.5" Y="7.523" /> | ||
386 | <Point X="11.562" Y="7.523" /> | ||
387 | <Point X="11.562" Y="7.273" /> | ||
388 | </Path> | ||
389 | <MemberNameLabel ManuallyPlaced="true"> | ||
390 | <Position X="0.068" Y="0.052" /> | ||
391 | </MemberNameLabel> | ||
392 | </AssociationLine> | ||
393 | <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInstallation" FixedToPoint="true"> | ||
394 | <Path> | ||
395 | <Point X="12.25" Y="6.813" /> | ||
396 | <Point X="15" Y="6.813" /> | ||
397 | </Path> | ||
398 | <MemberNameLabel ManuallyPlaced="true"> | ||
399 | <Position X="1.922" Y="0.05" /> | ||
400 | </MemberNameLabel> | ||
401 | </AssociationLine> | ||
402 | <TypeIdentifier> | ||
403 | <HashCode>UAIEYrAGAAAACQBAKEABAAoASA0ARQAASUIGBAAIIAA=</HashCode> | ||
404 | <FileName>ProductInstallation.cs</FileName> | ||
405 | </TypeIdentifier> | ||
406 | <ShowAsCollectionAssociation> | ||
407 | <Property Name="AllProducts" /> | ||
408 | <Property Name="Features" /> | ||
409 | </ShowAsCollectionAssociation> | ||
410 | </Class> | ||
411 | <Class Name="WixToolset.Dtf.WindowsInstaller.PatchInstallation"> | ||
412 | <Position X="12.5" Y="2.75" Width="2" /> | ||
413 | <Members> | ||
414 | <Method Name="EnumPatches" Hidden="true" /> | ||
415 | <Property Name="InstallationType" Hidden="true" /> | ||
416 | <Method Name="PatchInstallation" Hidden="true" /> | ||
417 | <Field Name="productCode" Hidden="true" /> | ||
418 | <Property Name="State" Hidden="true" /> | ||
419 | </Members> | ||
420 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true"> | ||
421 | <Path> | ||
422 | <Point X="13.562" Y="1.95" /> | ||
423 | <Point X="13.562" Y="2.75" /> | ||
424 | </Path> | ||
425 | </InheritanceLine> | ||
426 | <AssociationLine Name="AllPatches" Type="WixToolset.Dtf.WindowsInstaller.PatchInstallation" FixedFromPoint="true" FixedToPoint="true"> | ||
427 | <Path> | ||
428 | <Point X="12.938" Y="5.98" /> | ||
429 | <Point X="12.938" Y="6.23" /> | ||
430 | <Point X="14" Y="6.23" /> | ||
431 | <Point X="14" Y="5.98" /> | ||
432 | </Path> | ||
433 | <MemberNameLabel ManuallyPlaced="true"> | ||
434 | <Position X="0.093" Y="0.06" /> | ||
435 | </MemberNameLabel> | ||
436 | </AssociationLine> | ||
437 | <TypeIdentifier> | ||
438 | <HashCode>AAAgsgCACAAAAAAAKEAAAgAAAgAAAAAAAgIAFAAAIAA=</HashCode> | ||
439 | <FileName>PatchInstallation.cs</FileName> | ||
440 | </TypeIdentifier> | ||
441 | <ShowAsCollectionAssociation> | ||
442 | <Property Name="AllPatches" /> | ||
443 | </ShowAsCollectionAssociation> | ||
444 | </Class> | ||
445 | <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation"> | ||
446 | <Position X="17" Y="5" Width="2" /> | ||
447 | <Members> | ||
448 | <Method Name="ComponentInstallation" Hidden="true" /> | ||
449 | <Method Name="EnumClients" Hidden="true" /> | ||
450 | <Method Name="EnumComponents" Hidden="true" /> | ||
451 | <Method Name="EnumQualifiers" Hidden="true" /> | ||
452 | <Method Name="GetProductCode" Hidden="true" /> | ||
453 | </Members> | ||
454 | <Compartments> | ||
455 | <Compartment Name="Nested Types" Collapsed="false" /> | ||
456 | </Compartments> | ||
457 | <NestedTypes> | ||
458 | <Struct Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation.Qualifier" Collapsed="true"> | ||
459 | <TypeIdentifier> | ||
460 | <NewMemberFileName>ComponentInstallation.cs</NewMemberFileName> | ||
461 | </TypeIdentifier> | ||
462 | </Struct> | ||
463 | </NestedTypes> | ||
464 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true"> | ||
465 | <Path> | ||
466 | <Point X="18.111" Y="3.967" /> | ||
467 | <Point X="18.111" Y="5" /> | ||
468 | </Path> | ||
469 | </InheritanceLine> | ||
470 | <AssociationLine Name="AllComponents" Type="WixToolset.Dtf.WindowsInstaller.ComponentInstallation" FixedFromPoint="true" FixedToPoint="true"> | ||
471 | <Path> | ||
472 | <Point X="17.375" Y="7.443" /> | ||
473 | <Point X="17.375" Y="7.693" /> | ||
474 | <Point X="18.625" Y="7.693" /> | ||
475 | <Point X="18.625" Y="7.443" /> | ||
476 | </Path> | ||
477 | <MemberNameLabel ManuallyPlaced="true"> | ||
478 | <Position X="0.068" Y="0.052" /> | ||
479 | </MemberNameLabel> | ||
480 | </AssociationLine> | ||
481 | <AssociationLine Name="ClientProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true"> | ||
482 | <Path> | ||
483 | <Point X="17" Y="7.188" /> | ||
484 | <Point X="12.25" Y="7.188" /> | ||
485 | </Path> | ||
486 | <MemberNameLabel ManuallyPlaced="true" ManuallySized="true"> | ||
487 | <Position X="3.658" Y="-0.195" Height="0.16" Width="1.087" /> | ||
488 | </MemberNameLabel> | ||
489 | </AssociationLine> | ||
490 | <TypeIdentifier> | ||
491 | <HashCode>AUAAAAACAAAAAAIAEAgQAAQAAAAAAAAEAAAABAAAAAA=</HashCode> | ||
492 | <FileName>ComponentInstallation.cs</FileName> | ||
493 | </TypeIdentifier> | ||
494 | <ShowAsCollectionAssociation> | ||
495 | <Property Name="AllComponents" /> | ||
496 | <Property Name="ClientProducts" /> | ||
497 | </ShowAsCollectionAssociation> | ||
498 | </Class> | ||
499 | <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation"> | ||
500 | <Position X="15" Y="4.75" Width="1.75" /> | ||
501 | <Members> | ||
502 | <Method Name="FeatureInstallation" Hidden="true" /> | ||
503 | </Members> | ||
504 | <Compartments> | ||
505 | <Compartment Name="Nested Types" Collapsed="false" /> | ||
506 | </Compartments> | ||
507 | <NestedTypes> | ||
508 | <Struct Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation.UsageData" Collapsed="true"> | ||
509 | <TypeIdentifier> | ||
510 | <NewMemberFileName>FeatureInstallation.cs</NewMemberFileName> | ||
511 | </TypeIdentifier> | ||
512 | </Struct> | ||
513 | </NestedTypes> | ||
514 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true"> | ||
515 | <Path> | ||
516 | <Point X="17.889" Y="3.967" /> | ||
517 | <Point X="17.889" Y="4.804" /> | ||
518 | <Point X="16.75" Y="4.804" /> | ||
519 | </Path> | ||
520 | </InheritanceLine> | ||
521 | <TypeIdentifier> | ||
522 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAAEAAAAACAAAAAABAAAAAA=</HashCode> | ||
523 | <FileName>FeatureInstallation.cs</FileName> | ||
524 | </TypeIdentifier> | ||
525 | </Class> | ||
526 | <Class Name="WixToolset.Dtf.WindowsInstaller.SourceList"> | ||
527 | <Position X="14.75" Y="0.5" Width="2" /> | ||
528 | <Members> | ||
529 | <Method Name="ClearSourceType" Hidden="true" /> | ||
530 | <Method Name="Contains" Hidden="true" /> | ||
531 | <Method Name="CopyTo" Hidden="true" /> | ||
532 | <Method Name="EnumSources" Hidden="true" /> | ||
533 | <Field Name="installation" Hidden="true" /> | ||
534 | <Field Name="mediaList" Hidden="true" /> | ||
535 | <Method Name="SourceList" Hidden="true" /> | ||
536 | <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" /> | ||
537 | </Members> | ||
538 | <AssociationLine Name="MediaList" Type="WixToolset.Dtf.WindowsInstaller.MediaDisk" FixedFromPoint="true" FixedToPoint="true"> | ||
539 | <Path> | ||
540 | <Point X="16.75" Y="2.375" /> | ||
541 | <Point X="17.667" Y="2.375" /> | ||
542 | <Point X="17.667" Y="1.789" /> | ||
543 | </Path> | ||
544 | <MemberNameLabel ManuallyPlaced="true"> | ||
545 | <Position X="0.029" Y="0.377" /> | ||
546 | </MemberNameLabel> | ||
547 | </AssociationLine> | ||
548 | <TypeIdentifier> | ||
549 | <HashCode>BCIAAEAAEABAABgQCAAABIQAAFAQAAAAAAAABARwIgA=</HashCode> | ||
550 | <FileName>SourceList.cs</FileName> | ||
551 | </TypeIdentifier> | ||
552 | <ShowAsCollectionAssociation> | ||
553 | <Property Name="MediaList" /> | ||
554 | </ShowAsCollectionAssociation> | ||
555 | <Lollipop Position="0.2" /> | ||
556 | </Class> | ||
557 | <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInfo"> | ||
558 | <Position X="8" Y="5.5" Width="1.5" /> | ||
559 | <Members> | ||
560 | <Method Name="ComponentInfo" Hidden="true" /> | ||
561 | <Field Name="name" Hidden="true" /> | ||
562 | <Field Name="session" Hidden="true" /> | ||
563 | </Members> | ||
564 | <TypeIdentifier> | ||
565 | <HashCode>AQAAAAIAAAAAAAAAAAEAACQAAAAEAAAAAAAAAAAAAAA=</HashCode> | ||
566 | <FileName>ComponentInfo.cs</FileName> | ||
567 | </TypeIdentifier> | ||
568 | </Class> | ||
569 | <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInfo"> | ||
570 | <Position X="8" Y="7.5" Width="1.75" /> | ||
571 | <Members> | ||
572 | <Method Name="FeatureInfo" Hidden="true" /> | ||
573 | <Field Name="name" Hidden="true" /> | ||
574 | <Field Name="session" Hidden="true" /> | ||
575 | </Members> | ||
576 | <TypeIdentifier> | ||
577 | <HashCode>AQAAAAIAAAggAEAAAAEAACQQAAAEAAAAAAAAAAAAAAA=</HashCode> | ||
578 | <FileName>FeatureInfo.cs</FileName> | ||
579 | </TypeIdentifier> | ||
580 | </Class> | ||
581 | <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerException"> | ||
582 | <Position X="11.75" Y="7.5" Width="2.25" /> | ||
583 | <Members> | ||
584 | <Method Name="Combine" Hidden="true" /> | ||
585 | <Field Name="errorCode" Hidden="true" /> | ||
586 | <Field Name="errorData" Hidden="true" /> | ||
587 | <Method Name="ExceptionFromReturnCode" Hidden="true" /> | ||
588 | <Method Name="GetObjectData" Hidden="true" /> | ||
589 | <Method Name="GetSystemMessage" Hidden="true" /> | ||
590 | <Method Name="InstallerException" Hidden="true" /> | ||
591 | <Method Name="SaveErrorRecord" Hidden="true" /> | ||
592 | </Members> | ||
593 | <TypeIdentifier> | ||
594 | <HashCode>hAAAAAAAAAAAAAQAAAAAAAAgAAgIAAAIAAABIAAAQAA=</HashCode> | ||
595 | <FileName>Exceptions.cs</FileName> | ||
596 | </TypeIdentifier> | ||
597 | </Class> | ||
598 | <Class Name="WixToolset.Dtf.WindowsInstaller.BadQuerySyntaxException" Collapsed="true"> | ||
599 | <Position X="11.75" Y="9.75" Width="2.25" /> | ||
600 | <Members> | ||
601 | <Method Name="BadQuerySyntaxException" Hidden="true" /> | ||
602 | </Members> | ||
603 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true"> | ||
604 | <Path> | ||
605 | <Point X="12.938" Y="9.119" /> | ||
606 | <Point X="12.938" Y="9.75" /> | ||
607 | </Path> | ||
608 | </InheritanceLine> | ||
609 | <TypeIdentifier> | ||
610 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> | ||
611 | <FileName>Exceptions.cs</FileName> | ||
612 | </TypeIdentifier> | ||
613 | </Class> | ||
614 | <Class Name="WixToolset.Dtf.WindowsInstaller.InstallCanceledException" Collapsed="true"> | ||
615 | <Position X="14.25" Y="9.75" Width="2.25" /> | ||
616 | <Members> | ||
617 | <Method Name="InstallCanceledException" Hidden="true" /> | ||
618 | </Members> | ||
619 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true"> | ||
620 | <Path> | ||
621 | <Point X="13.375" Y="9.119" /> | ||
622 | <Point X="13.375" Y="9.631" /> | ||
623 | <Point X="14.25" Y="9.631" /> | ||
624 | <Point X="14.25" Y="9.813" /> | ||
625 | </Path> | ||
626 | </InheritanceLine> | ||
627 | <TypeIdentifier> | ||
628 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> | ||
629 | <FileName>Exceptions.cs</FileName> | ||
630 | </TypeIdentifier> | ||
631 | </Class> | ||
632 | <Class Name="WixToolset.Dtf.WindowsInstaller.InvalidHandleException" Collapsed="true"> | ||
633 | <Position X="14.25" Y="9" Width="2.25" /> | ||
634 | <Members> | ||
635 | <Method Name="InvalidHandleException" Hidden="true" /> | ||
636 | </Members> | ||
637 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true"> | ||
638 | <Path> | ||
639 | <Point X="13.812" Y="9.119" /> | ||
640 | <Point X="13.812" Y="9.375" /> | ||
641 | <Point X="14.25" Y="9.375" /> | ||
642 | </Path> | ||
643 | </InheritanceLine> | ||
644 | <TypeIdentifier> | ||
645 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> | ||
646 | <FileName>Exceptions.cs</FileName> | ||
647 | </TypeIdentifier> | ||
648 | </Class> | ||
649 | <Class Name="WixToolset.Dtf.WindowsInstaller.MergeException"> | ||
650 | <Position X="14.25" Y="7.5" Width="2.25" /> | ||
651 | <Members> | ||
652 | <Field Name="conflictCounts" Hidden="true" /> | ||
653 | <Field Name="conflictTables" Hidden="true" /> | ||
654 | <Method Name="GetObjectData" Hidden="true" /> | ||
655 | <Method Name="MergeException" Hidden="true" /> | ||
656 | </Members> | ||
657 | <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true"> | ||
658 | <Path> | ||
659 | <Point X="14" Y="8.813" /> | ||
660 | <Point X="14.25" Y="8.813" /> | ||
661 | </Path> | ||
662 | </InheritanceLine> | ||
663 | <TypeIdentifier> | ||
664 | <HashCode>AAAAAAAAAAAAAEAAEABAABAgAAAAAAAAAAAAIAAAAAA=</HashCode> | ||
665 | <FileName>Exceptions.cs</FileName> | ||
666 | </TypeIdentifier> | ||
667 | </Class> | ||
668 | <Struct Name="WixToolset.Dtf.WindowsInstaller.MediaDisk"> | ||
669 | <Position X="17" Y="0.5" Width="2" /> | ||
670 | <Members> | ||
671 | <Field Name="diskId" Hidden="true" /> | ||
672 | <Field Name="diskPrompt" Hidden="true" /> | ||
673 | <Method Name="MediaDisk" Hidden="true" /> | ||
674 | <Field Name="volumeLabel" Hidden="true" /> | ||
675 | </Members> | ||
676 | <TypeIdentifier> | ||
677 | <HashCode>AgAAAAAAAAAAAAAAAgAAIAAAACAAAAAEAAAABAAAAAA=</HashCode> | ||
678 | <FileName>MediaDisk.cs</FileName> | ||
679 | </TypeIdentifier> | ||
680 | </Struct> | ||
681 | <Struct Name="WixToolset.Dtf.WindowsInstaller.InstallCost"> | ||
682 | <Position X="10" Y="8" Width="1.25" /> | ||
683 | <Members> | ||
684 | <Field Name="cost" Hidden="true" /> | ||
685 | <Field Name="driveName" Hidden="true" /> | ||
686 | <Method Name="InstallCost" Hidden="true" /> | ||
687 | <Field Name="tempCost" Hidden="true" /> | ||
688 | </Members> | ||
689 | <TypeIdentifier> | ||
690 | <HashCode>AAAAAAAAAgAAAAIAAAAACBAAAAgQAAAAAAAAAAAAAAA=</HashCode> | ||
691 | <FileName>InstallCost.cs</FileName> | ||
692 | </TypeIdentifier> | ||
693 | </Struct> | ||
694 | <Struct Name="WixToolset.Dtf.WindowsInstaller.ShortcutTarget"> | ||
695 | <Position X="19.5" Y="7.75" Width="1.5" /> | ||
696 | <Members> | ||
697 | <Field Name="componentCode" Hidden="true" /> | ||
698 | <Method Name="Equals" Hidden="true" /> | ||
699 | <Field Name="feature" Hidden="true" /> | ||
700 | <Method Name="GetHashCode" Hidden="true" /> | ||
701 | <Method Name="operator !=" Hidden="true" /> | ||
702 | <Method Name="operator ==" Hidden="true" /> | ||
703 | <Field Name="productCode" Hidden="true" /> | ||
704 | <Method Name="ShortcutTarget" Hidden="true" /> | ||
705 | </Members> | ||
706 | <TypeIdentifier> | ||
707 | <HashCode>AAAgAAACAAAAAAAAgAAAAAAAAAAAAIAIAAIACAAAIiA=</HashCode> | ||
708 | <FileName>ShortcutTarget.cs</FileName> | ||
709 | </TypeIdentifier> | ||
710 | </Struct> | ||
711 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ActionResult" Collapsed="true"> | ||
712 | <Position X="22" Y="1" Width="2" /> | ||
713 | <TypeIdentifier> | ||
714 | <HashCode>AAAAAAAAAAAAAAQgAAAAAAAAAgAAAACAAAAAAAACAAA=</HashCode> | ||
715 | <FileName>Enums.cs</FileName> | ||
716 | </TypeIdentifier> | ||
717 | </Enum> | ||
718 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ControlAttributes" Collapsed="true"> | ||
719 | <Position X="24.25" Y="1" Width="2" /> | ||
720 | <TypeIdentifier> | ||
721 | <HashCode>AADAAlQAAAEgAoNFAAgAACAgAQAAAooMAAAAgCkCAkA=</HashCode> | ||
722 | <FileName>ColumnEnums.cs</FileName> | ||
723 | </TypeIdentifier> | ||
724 | </Enum> | ||
725 | <Enum Name="WixToolset.Dtf.WindowsInstaller.CustomActionTypes" Collapsed="true"> | ||
726 | <Position X="24.25" Y="1.5" Width="2" /> | ||
727 | <TypeIdentifier> | ||
728 | <HashCode>AABCCFAAAAAhJAAHAAAAAoAAAAAAAAABCAIEEQEAgAA=</HashCode> | ||
729 | <FileName>ColumnEnums.cs</FileName> | ||
730 | </TypeIdentifier> | ||
731 | </Enum> | ||
732 | <Enum Name="WixToolset.Dtf.WindowsInstaller.DatabaseOpenMode" Collapsed="true"> | ||
733 | <Position X="22" Y="1.5" Width="2" /> | ||
734 | <TypeIdentifier> | ||
735 | <HashCode>AAAKAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAYAAAAAAA=</HashCode> | ||
736 | <FileName>Enums.cs</FileName> | ||
737 | </TypeIdentifier> | ||
738 | </Enum> | ||
739 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallLogModes" Collapsed="true"> | ||
740 | <Position X="22" Y="2" Width="2" /> | ||
741 | <TypeIdentifier> | ||
742 | <HashCode>AAABgEBAAIAAAQBAAAAAABEAAQAAABSAgAAEAEWQAAA=</HashCode> | ||
743 | <FileName>Enums.cs</FileName> | ||
744 | </TypeIdentifier> | ||
745 | </Enum> | ||
746 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMessage" Collapsed="true"> | ||
747 | <Position X="22" Y="2.5" Width="2" /> | ||
748 | <TypeIdentifier> | ||
749 | <HashCode>AAABgABAAAAAAQBAAAAAABEAAQAAABSAgAAEAECAAAA=</HashCode> | ||
750 | <FileName>Enums.cs</FileName> | ||
751 | </TypeIdentifier> | ||
752 | </Enum> | ||
753 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMode" Collapsed="true"> | ||
754 | <Position X="22" Y="3" Width="2" /> | ||
755 | <TypeIdentifier> | ||
756 | <HashCode>AAAABAAAQAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAA=</HashCode> | ||
757 | <FileName>Enums.cs</FileName> | ||
758 | </TypeIdentifier> | ||
759 | </Enum> | ||
760 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallRunMode" Collapsed="true"> | ||
761 | <Position X="22" Y="3.5" Width="2" /> | ||
762 | <TypeIdentifier> | ||
763 | <HashCode>AAgAIEACAAQAAAQCAAAAAQAACEAABAQAgAECAAAAAgA=</HashCode> | ||
764 | <FileName>Enums.cs</FileName> | ||
765 | </TypeIdentifier> | ||
766 | </Enum> | ||
767 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallState" Collapsed="true"> | ||
768 | <Position X="22" Y="4" Width="2" /> | ||
769 | <TypeIdentifier> | ||
770 | <HashCode>AAAAABAAAgAAAFACgACQJAAAAABQAAQAAAAAAAAAABA=</HashCode> | ||
771 | <FileName>Enums.cs</FileName> | ||
772 | </TypeIdentifier> | ||
773 | </Enum> | ||
774 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallType" Collapsed="true"> | ||
775 | <Position X="22" Y="4.5" Width="2" /> | ||
776 | <TypeIdentifier> | ||
777 | <HashCode>AAAAAAAAAAAAAAAAIAAAIAAAAAAAAAAAAgAAAAAAAAA=</HashCode> | ||
778 | <FileName>Enums.cs</FileName> | ||
779 | </TypeIdentifier> | ||
780 | </Enum> | ||
781 | <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallUIOptions" Collapsed="true"> | ||
782 | <Position X="22" Y="5" Width="2" /> | ||
783 | <TypeIdentifier> | ||
784 | <HashCode>AAAAAAAIACAAAAAAQAAAIACEAAAAAAAAAgIAAAGAAAA=</HashCode> | ||
785 | <FileName>Enums.cs</FileName> | ||
786 | </TypeIdentifier> | ||
787 | </Enum> | ||
788 | <Enum Name="WixToolset.Dtf.WindowsInstaller.MessageResult" Collapsed="true"> | ||
789 | <Position X="22" Y="5.5" Width="2" /> | ||
790 | <TypeIdentifier> | ||
791 | <HashCode>AAAAAAAASAAQASBAAAAAAAAAACAAAAAAAAABAAEAAAA=</HashCode> | ||
792 | <FileName>Enums.cs</FileName> | ||
793 | </TypeIdentifier> | ||
794 | </Enum> | ||
795 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ReinstallModes" Collapsed="true"> | ||
796 | <Position X="22" Y="6" Width="2" /> | ||
797 | <TypeIdentifier> | ||
798 | <HashCode>IAAAAAAAAACAAAAAAAAQAAAQBAAAABAAAAEAAgAIABA=</HashCode> | ||
799 | <FileName>Enums.cs</FileName> | ||
800 | </TypeIdentifier> | ||
801 | </Enum> | ||
802 | <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformErrors" Collapsed="true"> | ||
803 | <Position X="22" Y="6.5" Width="2" /> | ||
804 | <TypeIdentifier> | ||
805 | <HashCode>AAIEAAAAAAABAAAAAAAAAAQAAAAAAAACAAAgAAEAABA=</HashCode> | ||
806 | <FileName>Enums.cs</FileName> | ||
807 | </TypeIdentifier> | ||
808 | </Enum> | ||
809 | <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformValidations" Collapsed="true"> | ||
810 | <Position X="22" Y="7" Width="2" /> | ||
811 | <TypeIdentifier> | ||
812 | <HashCode>AAACAAAACAQIAAAAAAAAAAAAAEgAAAQAAIAQAQEAAAQ=</HashCode> | ||
813 | <FileName>Enums.cs</FileName> | ||
814 | </TypeIdentifier> | ||
815 | </Enum> | ||
816 | <Enum Name="WixToolset.Dtf.WindowsInstaller.UserContexts" Collapsed="true"> | ||
817 | <Position X="22" Y="7.5" Width="2" /> | ||
818 | <TypeIdentifier> | ||
819 | <HashCode>AAAAAAgAAAAAAAAEAAAAAAIAAAAAAAAAAIAAQAEAAAA=</HashCode> | ||
820 | <FileName>Enums.cs</FileName> | ||
821 | </TypeIdentifier> | ||
822 | </Enum> | ||
823 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ValidationError" Collapsed="true"> | ||
824 | <Position X="22" Y="8" Width="2" /> | ||
825 | <TypeIdentifier> | ||
826 | <HashCode>oCMBAAABEAMAAAAAAAAQAAIASigAABKBABgCgEEAAA4=</HashCode> | ||
827 | <FileName>Enums.cs</FileName> | ||
828 | </TypeIdentifier> | ||
829 | </Enum> | ||
830 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ViewModifyMode" Collapsed="true"> | ||
831 | <Position X="22" Y="8.5" Width="2" /> | ||
832 | <TypeIdentifier> | ||
833 | <HashCode>IAQAAEQAAADgAAAACAAAAACAAAEAAQAAAAAAAAAAACA=</HashCode> | ||
834 | <FileName>Enums.cs</FileName> | ||
835 | </TypeIdentifier> | ||
836 | </Enum> | ||
837 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ComponentAttributes" Collapsed="true"> | ||
838 | <Position X="24.25" Y="2" Width="2" /> | ||
839 | <TypeIdentifier> | ||
840 | <HashCode>AAABAAAAIAAAAAAAEAAACAAAIAAAgQCAAAAgAAEAAAA=</HashCode> | ||
841 | <FileName>ColumnEnums.cs</FileName> | ||
842 | </TypeIdentifier> | ||
843 | </Enum> | ||
844 | <Enum Name="WixToolset.Dtf.WindowsInstaller.DialogAttributes" Collapsed="true"> | ||
845 | <Position X="24.25" Y="2.5" Width="2" /> | ||
846 | <TypeIdentifier> | ||
847 | <HashCode>AAAIAAQAAAAAAQIEAAAAACAAEAAAAAAIgAAggAAAAgA=</HashCode> | ||
848 | <FileName>ColumnEnums.cs</FileName> | ||
849 | </TypeIdentifier> | ||
850 | </Enum> | ||
851 | <Enum Name="WixToolset.Dtf.WindowsInstaller.FeatureAttributes" Collapsed="true"> | ||
852 | <Position X="24.25" Y="3" Width="2" /> | ||
853 | <TypeIdentifier> | ||
854 | <HashCode>AAAAQEAAAEAAAAAAAAAAAAAAEAAAAAABABAAAAEAAAA=</HashCode> | ||
855 | <FileName>ColumnEnums.cs</FileName> | ||
856 | </TypeIdentifier> | ||
857 | </Enum> | ||
858 | <Enum Name="WixToolset.Dtf.WindowsInstaller.FileAttributes" Collapsed="true"> | ||
859 | <Position X="24.25" Y="3.5" Width="2" /> | ||
860 | <TypeIdentifier> | ||
861 | <HashCode>AAAAAABAAIAIAAAAAAAAAgAAAAAEAAAAAAEIAAEEAAA=</HashCode> | ||
862 | <FileName>ColumnEnums.cs</FileName> | ||
863 | </TypeIdentifier> | ||
864 | </Enum> | ||
865 | <Enum Name="WixToolset.Dtf.WindowsInstaller.IniFileAction" Collapsed="true"> | ||
866 | <Position X="24.25" Y="4" Width="2" /> | ||
867 | <TypeIdentifier> | ||
868 | <HashCode>AggAAAAAAAAAEAEAAAAAAAAAAAABAAAAAAAAAAAAAAA=</HashCode> | ||
869 | <FileName>ColumnEnums.cs</FileName> | ||
870 | </TypeIdentifier> | ||
871 | </Enum> | ||
872 | <Enum Name="WixToolset.Dtf.WindowsInstaller.LocatorTypes" Collapsed="true"> | ||
873 | <Position X="24.25" Y="4.5" Width="2" /> | ||
874 | <TypeIdentifier> | ||
875 | <HashCode>ABAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAABAAAA=</HashCode> | ||
876 | <FileName>ColumnEnums.cs</FileName> | ||
877 | </TypeIdentifier> | ||
878 | </Enum> | ||
879 | <Enum Name="WixToolset.Dtf.WindowsInstaller.RegistryRoot" Collapsed="true"> | ||
880 | <Position X="24.25" Y="5" Width="2" /> | ||
881 | <TypeIdentifier> | ||
882 | <HashCode>AACgACAAAIAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAA=</HashCode> | ||
883 | <FileName>ColumnEnums.cs</FileName> | ||
884 | </TypeIdentifier> | ||
885 | </Enum> | ||
886 | <Enum Name="WixToolset.Dtf.WindowsInstaller.RemoveFileModes" Collapsed="true"> | ||
887 | <Position X="24.25" Y="5.5" Width="2" /> | ||
888 | <TypeIdentifier> | ||
889 | <HashCode>AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAACAAAEAAAA=</HashCode> | ||
890 | <FileName>ColumnEnums.cs</FileName> | ||
891 | </TypeIdentifier> | ||
892 | </Enum> | ||
893 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceAttributes" Collapsed="true"> | ||
894 | <Position X="24.25" Y="6" Width="2" /> | ||
895 | <TypeIdentifier> | ||
896 | <HashCode>AQCAAAAAQAAAAAAAAACAAAgAAAAAAAAAAgAAABEAAAI=</HashCode> | ||
897 | <FileName>ColumnEnums.cs</FileName> | ||
898 | </TypeIdentifier> | ||
899 | </Enum> | ||
900 | <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceControlEvents" Collapsed="true"> | ||
901 | <Position X="24.25" Y="6.5" Width="2" /> | ||
902 | <TypeIdentifier> | ||
903 | <HashCode>AAAAAAAAACAAAAAAAAAAEAAAAAEAAgAAJAAAAAEAAAA=</HashCode> | ||
904 | <FileName>ColumnEnums.cs</FileName> | ||
905 | </TypeIdentifier> | ||
906 | </Enum> | ||
907 | <Enum Name="WixToolset.Dtf.WindowsInstaller.TextStyles" Collapsed="true"> | ||
908 | <Position X="24.25" Y="7" Width="2" /> | ||
909 | <TypeIdentifier> | ||
910 | <HashCode>AAAAAAAAAAAAACAAAIAAAAAAAAACAAAAAAAAAAAAAAE=</HashCode> | ||
911 | <FileName>ColumnEnums.cs</FileName> | ||
912 | </TypeIdentifier> | ||
913 | </Enum> | ||
914 | <Enum Name="WixToolset.Dtf.WindowsInstaller.UpgradeAttributes" Collapsed="true"> | ||
915 | <Position X="24.25" Y="7.5" Width="2" /> | ||
916 | <TypeIdentifier> | ||
917 | <HashCode>AEAAAAAAAAAAAAAQAAAAAAAAAAgIAAAAABAAAAAAIAA=</HashCode> | ||
918 | <FileName>ColumnEnums.cs</FileName> | ||
919 | </TypeIdentifier> | ||
920 | </Enum> | ||
921 | <Delegate Name="WixToolset.Dtf.WindowsInstaller.InapplicablePatchHandler" Collapsed="true"> | ||
922 | <Position X="16.75" Y="9.75" Width="2.25" /> | ||
923 | <TypeIdentifier> | ||
924 | <HashCode>AAAAAAAAAACAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAA=</HashCode> | ||
925 | <FileName>Installer.cs</FileName> | ||
926 | </TypeIdentifier> | ||
927 | </Delegate> | ||
928 | <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIHandler" Collapsed="true"> | ||
929 | <Position X="16.75" Y="8.5" Width="2.25" /> | ||
930 | <TypeIdentifier> | ||
931 | <HashCode>AAAEAAAAAAAAABAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode> | ||
932 | <FileName>ExternalUIHandler.cs</FileName> | ||
933 | </TypeIdentifier> | ||
934 | </Delegate> | ||
935 | <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIRecordHandler" Collapsed="true"> | ||
936 | <Position X="16.75" Y="9" Width="2.25" /> | ||
937 | <TypeIdentifier> | ||
938 | <HashCode>AAAEAAAAAAAAAAAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode> | ||
939 | <FileName>ExternalUIHandler.cs</FileName> | ||
940 | </TypeIdentifier> | ||
941 | </Delegate> | ||
942 | <Font Name="Verdana" Size="8" /> | ||
943 | </ClassDiagram> \ No newline at end of file | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj b/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj new file mode 100644 index 00000000..515e609b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj | |||
@@ -0,0 +1,30 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- 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. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <RootNamespace>WixToolset.Dtf.WindowsInstaller</RootNamespace> | ||
7 | <AssemblyName>WixToolset.Dtf.WindowsInstaller</AssemblyName> | ||
8 | <TargetFrameworks>netstandard2.0;net20</TargetFrameworks> | ||
9 | <Description>Managed libraries for Windows Installer</Description> | ||
10 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
11 | </PropertyGroup> | ||
12 | |||
13 | <ItemGroup> | ||
14 | <EmbeddedResource Include="Errors.resources" /> | ||
15 | </ItemGroup> | ||
16 | |||
17 | <ItemGroup> | ||
18 | <None Include="Errors.txt" /> | ||
19 | <None Include="WindowsInstaller.cd" /> | ||
20 | </ItemGroup> | ||
21 | |||
22 | <ItemGroup> | ||
23 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
24 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
25 | </ItemGroup> | ||
26 | |||
27 | <ItemGroup Condition=" '$(TargetFramework)'=='net20' "> | ||
28 | <Reference Include="System.Configuration" /> | ||
29 | </ItemGroup> | ||
30 | </Project> | ||
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs new file mode 100644 index 00000000..88a0295d --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs | |||
@@ -0,0 +1,469 @@ | |||
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.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Xml; | ||
8 | using System.Xml.Serialization; | ||
9 | using System.Text; | ||
10 | using System.Collections.Generic; | ||
11 | using System.Globalization; | ||
12 | using System.Diagnostics.CodeAnalysis; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Contains a collection of key-value pairs suitable for passing between | ||
16 | /// immediate and deferred/rollback/commit custom actions. | ||
17 | /// </summary> | ||
18 | /// <remarks> | ||
19 | /// Call the <see cref="CustomActionData.ToString" /> method to get a string | ||
20 | /// suitable for storing in a property and reconstructing the custom action data later. | ||
21 | /// </remarks> | ||
22 | /// <seealso cref="Session.CustomActionData"/> | ||
23 | /// <seealso cref="Session.DoAction(string,CustomActionData)"/> | ||
24 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] | ||
25 | public sealed class CustomActionData : IDictionary<string, string> | ||
26 | { | ||
27 | /// <summary> | ||
28 | /// "CustomActionData" literal property name. | ||
29 | /// </summary> | ||
30 | public const string PropertyName = "CustomActionData"; | ||
31 | |||
32 | private const char DataSeparator = ';'; | ||
33 | private const char KeyValueSeparator = '='; | ||
34 | |||
35 | private IDictionary<string, string> data; | ||
36 | |||
37 | /// <summary> | ||
38 | /// Creates a new empty custom action data object. | ||
39 | /// </summary> | ||
40 | public CustomActionData() : this(null) | ||
41 | { | ||
42 | } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Reconstructs a custom action data object from data that was previously | ||
46 | /// persisted in a string. | ||
47 | /// </summary> | ||
48 | /// <param name="keyValueList">Previous output from <see cref="CustomActionData.ToString" />.</param> | ||
49 | public CustomActionData(string keyValueList) | ||
50 | { | ||
51 | this.data = new Dictionary<string, string>(); | ||
52 | |||
53 | if (keyValueList != null) | ||
54 | { | ||
55 | this.Parse(keyValueList); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | /// <summary> | ||
60 | /// Adds a key and value to the data collection. | ||
61 | /// </summary> | ||
62 | /// <param name="key">Case-sensitive data key.</param> | ||
63 | /// <param name="value">Data value (may be null).</param> | ||
64 | /// <exception cref="ArgumentException">the key does not consist solely of letters, | ||
65 | /// numbers, and the period, underscore, and space characters.</exception> | ||
66 | public void Add(string key, string value) | ||
67 | { | ||
68 | CustomActionData.ValidateKey(key); | ||
69 | this.data.Add(key, value); | ||
70 | } | ||
71 | |||
72 | /// <summary> | ||
73 | /// Adds a value to the data collection, using XML serialization to persist the object as a string. | ||
74 | /// </summary> | ||
75 | /// <param name="key">Case-sensitive data key.</param> | ||
76 | /// <param name="value">Data value (may be null).</param> | ||
77 | /// <exception cref="ArgumentException">the key does not consist solely of letters, | ||
78 | /// numbers, and the period, underscore, and space characters.</exception> | ||
79 | /// <exception cref="NotSupportedException">The value type does not support XML serialization.</exception> | ||
80 | /// <exception cref="InvalidOperationException">The value could not be serialized.</exception> | ||
81 | public void AddObject<T>(string key, T value) | ||
82 | { | ||
83 | if (value == null) | ||
84 | { | ||
85 | this.Add(key, null); | ||
86 | } | ||
87 | else if (typeof(T) == typeof(string) || | ||
88 | typeof(T) == typeof(CustomActionData)) // Serialize nested CustomActionData | ||
89 | { | ||
90 | this.Add(key, value.ToString()); | ||
91 | } | ||
92 | else | ||
93 | { | ||
94 | string valueString = CustomActionData.Serialize<T>(value); | ||
95 | this.Add(key, valueString); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Gets a value from the data collection, using XML serialization to load the object from a string. | ||
101 | /// </summary> | ||
102 | /// <param name="key">Case-sensitive data key.</param> | ||
103 | /// <exception cref="InvalidOperationException">The value could not be deserialized.</exception> | ||
104 | [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] | ||
105 | public T GetObject<T>(string key) | ||
106 | { | ||
107 | string value = this[key]; | ||
108 | if (value == null) | ||
109 | { | ||
110 | return default(T); | ||
111 | } | ||
112 | else if (typeof(T) == typeof(string)) | ||
113 | { | ||
114 | // Funny casting because the compiler doesn't know T is string here. | ||
115 | return (T) (object) value; | ||
116 | } | ||
117 | else if (typeof(T) == typeof(CustomActionData)) | ||
118 | { | ||
119 | // Deserialize nested CustomActionData. | ||
120 | return (T) (object) new CustomActionData(value); | ||
121 | } | ||
122 | else if (value.Length == 0) | ||
123 | { | ||
124 | return default(T); | ||
125 | } | ||
126 | else | ||
127 | { | ||
128 | return CustomActionData.Deserialize<T>(value); | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /// <summary> | ||
133 | /// Determines whether the data contains an item with the specified key. | ||
134 | /// </summary> | ||
135 | /// <param name="key">Case-sensitive data key.</param> | ||
136 | /// <returns>true if the data contains an item with the key; otherwise, false</returns> | ||
137 | public bool ContainsKey(string key) | ||
138 | { | ||
139 | return this.data.ContainsKey(key); | ||
140 | } | ||
141 | |||
142 | /// <summary> | ||
143 | /// Gets a collection object containing all the keys of the data. | ||
144 | /// </summary> | ||
145 | public ICollection<string> Keys | ||
146 | { | ||
147 | get | ||
148 | { | ||
149 | return this.data.Keys; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Removes the item with the specified key from the data. | ||
155 | /// </summary> | ||
156 | /// <param name="key">Case-sensitive data key.</param> | ||
157 | /// <returns>true if the item was successfully removed from the data; | ||
158 | /// false if an item with the specified key was not found</returns> | ||
159 | public bool Remove(string key) | ||
160 | { | ||
161 | return this.data.Remove(key); | ||
162 | } | ||
163 | |||
164 | /// <summary> | ||
165 | /// Gets the value with the specified key. | ||
166 | /// </summary> | ||
167 | /// <param name="key">Case-sensitive data key.</param> | ||
168 | /// <param name="value">Value associated with the specified key, or | ||
169 | /// null if an item with the specified key was not found</param> | ||
170 | /// <returns>true if the data contains an item with the specified key; otherwise, false.</returns> | ||
171 | public bool TryGetValue(string key, out string value) | ||
172 | { | ||
173 | return this.data.TryGetValue(key, out value); | ||
174 | } | ||
175 | |||
176 | /// <summary> | ||
177 | /// Gets a collection containing all the values of the data. | ||
178 | /// </summary> | ||
179 | public ICollection<string> Values | ||
180 | { | ||
181 | get | ||
182 | { | ||
183 | return this.data.Values; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Gets or sets a data value with a specified key. | ||
189 | /// </summary> | ||
190 | /// <param name="key">Case-sensitive data key.</param> | ||
191 | /// <exception cref="ArgumentException">the key does not consist solely of letters, | ||
192 | /// numbers, and the period, underscore, and space characters.</exception> | ||
193 | public string this[string key] | ||
194 | { | ||
195 | get | ||
196 | { | ||
197 | return this.data[key]; | ||
198 | } | ||
199 | set | ||
200 | { | ||
201 | CustomActionData.ValidateKey(key); | ||
202 | this.data[key] = value; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | /// <summary> | ||
207 | /// Adds an item with key and value to the data collection. | ||
208 | /// </summary> | ||
209 | /// <param name="item">Case-sensitive data key, with a data value that may be null.</param> | ||
210 | /// <exception cref="ArgumentException">the key does not consist solely of letters, | ||
211 | /// numbers, and the period, underscore, and space characters.</exception> | ||
212 | public void Add(KeyValuePair<string, string> item) | ||
213 | { | ||
214 | CustomActionData.ValidateKey(item.Key); | ||
215 | this.data.Add(item); | ||
216 | } | ||
217 | |||
218 | /// <summary> | ||
219 | /// Removes all items from the data. | ||
220 | /// </summary> | ||
221 | public void Clear() | ||
222 | { | ||
223 | if (this.data.Count > 0) | ||
224 | { | ||
225 | this.data.Clear(); | ||
226 | } | ||
227 | } | ||
228 | |||
229 | /// <summary> | ||
230 | /// Determines whether the data contains a specified item. | ||
231 | /// </summary> | ||
232 | /// <param name="item">The data item to locate.</param> | ||
233 | /// <returns>true if the data contains the item; otherwise, false</returns> | ||
234 | public bool Contains(KeyValuePair<string, string> item) | ||
235 | { | ||
236 | return this.data.Contains(item); | ||
237 | } | ||
238 | |||
239 | /// <summary> | ||
240 | /// Copies the data to an array, starting at a particular array index. | ||
241 | /// </summary> | ||
242 | /// <param name="array">Destination array.</param> | ||
243 | /// <param name="arrayIndex">Index in the array at which copying begins.</param> | ||
244 | public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) | ||
245 | { | ||
246 | this.data.CopyTo(array, arrayIndex); | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Gets the number of items in the data. | ||
251 | /// </summary> | ||
252 | public int Count | ||
253 | { | ||
254 | get | ||
255 | { | ||
256 | return this.data.Count; | ||
257 | } | ||
258 | } | ||
259 | |||
260 | /// <summary> | ||
261 | /// Gets a value indicating whether the data is read-only. | ||
262 | /// </summary> | ||
263 | public bool IsReadOnly | ||
264 | { | ||
265 | get | ||
266 | { | ||
267 | return false; | ||
268 | } | ||
269 | } | ||
270 | |||
271 | /// <summary> | ||
272 | /// Removes an item from the data. | ||
273 | /// </summary> | ||
274 | /// <param name="item">The item to remove.</param> | ||
275 | /// <returns>true if the item was successfully removed from the data; | ||
276 | /// false if the item was not found</returns> | ||
277 | public bool Remove(KeyValuePair<string, string> item) | ||
278 | { | ||
279 | return this.data.Remove(item); | ||
280 | } | ||
281 | |||
282 | /// <summary> | ||
283 | /// Returns an enumerator that iterates through the collection. | ||
284 | /// </summary> | ||
285 | /// <returns>An enumerator that can be used to iterate through the collection.</returns> | ||
286 | public IEnumerator<KeyValuePair<string, string>> GetEnumerator() | ||
287 | { | ||
288 | return this.data.GetEnumerator(); | ||
289 | } | ||
290 | |||
291 | /// <summary> | ||
292 | /// Returns an enumerator that iterates through the collection. | ||
293 | /// </summary> | ||
294 | /// <returns>An enumerator that can be used to iterate through the collection.</returns> | ||
295 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
296 | { | ||
297 | return ((System.Collections.IEnumerable) this.data).GetEnumerator(); | ||
298 | } | ||
299 | |||
300 | /// <summary> | ||
301 | /// Gets a string representation of the data suitable for persisting in a property. | ||
302 | /// </summary> | ||
303 | /// <returns>Data string in the form "Key1=Value1;Key2=Value2"</returns> | ||
304 | public override string ToString() | ||
305 | { | ||
306 | StringBuilder buf = new StringBuilder(); | ||
307 | |||
308 | foreach (KeyValuePair<string, string> item in this.data) | ||
309 | { | ||
310 | if (buf.Length > 0) | ||
311 | { | ||
312 | buf.Append(CustomActionData.DataSeparator); | ||
313 | } | ||
314 | |||
315 | buf.Append(item.Key); | ||
316 | |||
317 | if (item.Value != null) | ||
318 | { | ||
319 | buf.Append(CustomActionData.KeyValueSeparator); | ||
320 | buf.Append(CustomActionData.Escape(item.Value)); | ||
321 | } | ||
322 | } | ||
323 | |||
324 | return buf.ToString(); | ||
325 | } | ||
326 | |||
327 | /// <summary> | ||
328 | /// Ensures that a key contains valid characters. | ||
329 | /// </summary> | ||
330 | /// <param name="key">key to be validated</param> | ||
331 | /// <exception cref="ArgumentException">the key does not consist solely of letters, | ||
332 | /// numbers, and the period, underscore, and space characters.</exception> | ||
333 | private static void ValidateKey(string key) | ||
334 | { | ||
335 | if (String.IsNullOrEmpty(key)) | ||
336 | { | ||
337 | throw new ArgumentNullException("key"); | ||
338 | } | ||
339 | |||
340 | for (int i = 0; i < key.Length; i++) | ||
341 | { | ||
342 | char c = key[i]; | ||
343 | if (!Char.IsLetterOrDigit(c) && c != '_' && c != '.' && | ||
344 | !(i > 0 && i < key.Length - 1 && c == ' ')) | ||
345 | { | ||
346 | throw new ArgumentOutOfRangeException("key"); | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /// <summary> | ||
352 | /// Serializes a value into an XML string. | ||
353 | /// </summary> | ||
354 | /// <typeparam name="T">Type of the value.</typeparam> | ||
355 | /// <param name="value">Value to be serialized.</param> | ||
356 | /// <returns>Serialized value data as a string.</returns> | ||
357 | private static string Serialize<T>(T value) | ||
358 | { | ||
359 | XmlWriterSettings xws = new XmlWriterSettings(); | ||
360 | xws.OmitXmlDeclaration = true; | ||
361 | |||
362 | StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); | ||
363 | using (XmlWriter xw = XmlWriter.Create(sw, xws)) | ||
364 | { | ||
365 | XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); | ||
366 | ns.Add(string.Empty, String.Empty); // Prevent output of any namespaces | ||
367 | |||
368 | XmlSerializer ser = new XmlSerializer(typeof(T)); | ||
369 | ser.Serialize(xw, value, ns); | ||
370 | |||
371 | return sw.ToString(); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | /// <summary> | ||
376 | /// Deserializes a value from an XML string. | ||
377 | /// </summary> | ||
378 | /// <typeparam name="T">Expected type of the value.</typeparam> | ||
379 | /// <param name="value">Serialized value data.</param> | ||
380 | /// <returns>Deserialized value object.</returns> | ||
381 | private static T Deserialize<T>(string value) | ||
382 | { | ||
383 | StringReader sr = new StringReader(value); | ||
384 | using (XmlReader xr = XmlReader.Create(sr)) | ||
385 | { | ||
386 | XmlSerializer ser = new XmlSerializer(typeof(T)); | ||
387 | return (T) ser.Deserialize(xr); | ||
388 | } | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Escapes a value string by doubling any data-separator (semicolon) characters. | ||
393 | /// </summary> | ||
394 | /// <param name="value"></param> | ||
395 | /// <returns>Escaped value string</returns> | ||
396 | private static string Escape(string value) | ||
397 | { | ||
398 | value = value.Replace(String.Empty + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator); | ||
399 | return value; | ||
400 | } | ||
401 | |||
402 | /// <summary> | ||
403 | /// Unescapes a value string by undoubling any doubled data-separator (semicolon) characters. | ||
404 | /// </summary> | ||
405 | /// <param name="value"></param> | ||
406 | /// <returns>Unescaped value string</returns> | ||
407 | private static string Unescape(string value) | ||
408 | { | ||
409 | value = value.Replace(String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator); | ||
410 | return value; | ||
411 | } | ||
412 | |||
413 | /// <summary> | ||
414 | /// Loads key-value pairs from a string into the data collection. | ||
415 | /// </summary> | ||
416 | /// <param name="keyValueList">key-value pair list of the form returned by <see cref="ToString"/></param> | ||
417 | private void Parse(string keyValueList) | ||
418 | { | ||
419 | int itemStart = 0; | ||
420 | while (itemStart < keyValueList.Length) | ||
421 | { | ||
422 | // Find the next non-escaped data separator. | ||
423 | int semi = itemStart - 2; | ||
424 | do | ||
425 | { | ||
426 | semi = keyValueList.IndexOf(CustomActionData.DataSeparator, semi + 2); | ||
427 | } | ||
428 | while (semi >= 0 && semi < keyValueList.Length - 1 && keyValueList[semi + 1] == CustomActionData.DataSeparator); | ||
429 | |||
430 | if (semi < 0) | ||
431 | { | ||
432 | semi = keyValueList.Length; | ||
433 | } | ||
434 | |||
435 | // Find the next non-escaped key-value separator. | ||
436 | int equals = itemStart - 2; | ||
437 | do | ||
438 | { | ||
439 | equals = keyValueList.IndexOf(CustomActionData.KeyValueSeparator, equals + 2); | ||
440 | } | ||
441 | while (equals >= 0 && equals < keyValueList.Length - 1 && keyValueList[equals + 1] == CustomActionData.KeyValueSeparator); | ||
442 | |||
443 | if (equals < 0 || equals > semi) | ||
444 | { | ||
445 | equals = semi; | ||
446 | } | ||
447 | |||
448 | string key = keyValueList.Substring(itemStart, equals - itemStart); | ||
449 | string value = null; | ||
450 | |||
451 | // If there's a key-value separator before the next data separator, then the item has a value. | ||
452 | if (equals < semi) | ||
453 | { | ||
454 | value = keyValueList.Substring(equals + 1, semi - (equals + 1)); | ||
455 | value = CustomActionData.Unescape(value); | ||
456 | } | ||
457 | |||
458 | // Add non-duplicate items to the collection. | ||
459 | if (key.Length > 0 && !this.data.ContainsKey(key)) | ||
460 | { | ||
461 | this.data.Add(key, value); | ||
462 | } | ||
463 | |||
464 | // Move past the data separator to the next item. | ||
465 | itemStart = semi + 1; | ||
466 | } | ||
467 | } | ||
468 | } | ||
469 | } | ||