aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs890
1 files changed, 890 insertions, 0 deletions
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
3namespace 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>
31public delegate void InapplicablePatchHandler(string patch, Exception exception);
32
33/// <summary>
34/// Provides static methods for installing and configuring products and patches.
35/// </summary>
36public 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}