diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs | 946 |
1 files changed, 946 insertions, 0 deletions
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 | } | ||