aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs946
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
3namespace 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}