aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs801
1 files changed, 801 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs
new file mode 100644
index 00000000..27739e17
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs
@@ -0,0 +1,801 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// Represents a unique instance of a product that
13 /// is either advertised, installed or unknown.
14 /// </summary>
15 public class ProductInstallation : Installation
16 {
17 /// <summary>
18 /// Gets the set of all products with a specified upgrade code. This method lists the
19 /// currently installed and advertised products that have the specified UpgradeCode
20 /// property in their Property table.
21 /// </summary>
22 /// <param name="upgradeCode">Upgrade code of related products</param>
23 /// <returns>Enumeration of product codes</returns>
24 /// <remarks><p>
25 /// Win32 MSI API:
26 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumrelatedproducts.asp">MsiEnumRelatedProducts</a>
27 /// </p></remarks>
28 public static IEnumerable<ProductInstallation> GetRelatedProducts(string upgradeCode)
29 {
30 StringBuilder buf = new StringBuilder(40);
31 for (uint i = 0; true; i++)
32 {
33 uint ret = NativeMethods.MsiEnumRelatedProducts(upgradeCode, 0, i, buf);
34 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
35 if (ret != 0)
36 {
37 throw InstallerException.ExceptionFromReturnCode(ret);
38 }
39 yield return new ProductInstallation(buf.ToString());
40 }
41 }
42
43 /// <summary>
44 /// Enumerates all product installations on the system.
45 /// </summary>
46 /// <returns>An enumeration of product objects.</returns>
47 /// <remarks><p>
48 /// Win32 MSI API:
49 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproducts.asp">MsiEnumProducts</a>,
50 /// </p></remarks>
51 public static IEnumerable<ProductInstallation> AllProducts
52 {
53 get
54 {
55 return GetProducts(null, null, UserContexts.All);
56 }
57 }
58
59 /// <summary>
60 /// Enumerates product installations based on certain criteria.
61 /// </summary>
62 /// <param name="productCode">ProductCode (GUID) of the product instances to be enumerated. Only
63 /// instances of products within the scope of the context specified by the
64 /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be
65 /// enumerated. This parameter may be set to null to enumerate all products in the specified
66 /// context.</param>
67 /// <param name="userSid">Specifies a security identifier (SID) that restricts the context
68 /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts
69 /// enumeration to the current user or any user in the system. The special SID string
70 /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter
71 /// can be set to null to restrict the enumeration scope to the current user. When
72 /// <paramref name="context"/> is set to the machine context only,
73 /// <paramref name="userSid"/> must be null.</param>
74 /// <param name="context">Specifies the user context.</param>
75 /// <returns>An enumeration of product objects for enumerated product instances.</returns>
76 /// <remarks><p>
77 /// Win32 MSI API:
78 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproductsex.asp">MsiEnumProductsEx</a>
79 /// </p></remarks>
80 public static IEnumerable<ProductInstallation> GetProducts(
81 string productCode, string userSid, UserContexts context)
82 {
83 StringBuilder buf = new StringBuilder(40);
84 UserContexts targetContext;
85 StringBuilder targetSidBuf = new StringBuilder(40);
86 for (uint i = 0; ; i++)
87 {
88 uint targetSidBufSize = (uint) targetSidBuf.Capacity;
89 uint ret = NativeMethods.MsiEnumProductsEx(
90 productCode,
91 userSid,
92 context,
93 i,
94 buf,
95 out targetContext,
96 targetSidBuf,
97 ref targetSidBufSize);
98 if (ret == (uint) NativeMethods.Error.MORE_DATA)
99 {
100 targetSidBuf.Capacity = (int) ++targetSidBufSize;
101 ret = NativeMethods.MsiEnumProductsEx(
102 productCode,
103 userSid,
104 context,
105 i,
106 buf,
107 out targetContext,
108 targetSidBuf,
109 ref targetSidBufSize);
110 }
111
112 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
113 {
114 break;
115 }
116
117 if (ret != 0)
118 {
119 throw InstallerException.ExceptionFromReturnCode(ret);
120 }
121
122 yield return new ProductInstallation(
123 buf.ToString(),
124 targetSidBuf.ToString(),
125 targetContext);
126 }
127 }
128
129 private IDictionary<string, string> properties;
130
131 /// <summary>
132 /// Creates a new object for accessing information about a product installation on the current system.
133 /// </summary>
134 /// <param name="productCode">ProductCode (GUID) of the product.</param>
135 /// <remarks><p>
136 /// All available user contexts will be queried.
137 /// </p></remarks>
138 public ProductInstallation(string productCode)
139 : this(productCode, null, UserContexts.All)
140 {
141 }
142
143 /// <summary>
144 /// Creates a new object for accessing information about a product installation on the current system.
145 /// </summary>
146 /// <param name="productCode">ProductCode (GUID) of the product.</param>
147 /// <param name="userSid">The specific user, when working in a user context. This
148 /// parameter may be null to indicate the current user. The parameter must be null
149 /// when working in a machine context.</param>
150 /// <param name="context">The user context. The calling process must have administrative
151 /// privileges to get information for a product installed for a user other than the
152 /// current user.</param>
153 public ProductInstallation(string productCode, string userSid, UserContexts context)
154 : base(productCode, userSid, context)
155 {
156 if (String.IsNullOrEmpty(productCode))
157 {
158 throw new ArgumentNullException("productCode");
159 }
160 }
161
162 internal ProductInstallation(IDictionary<string, string> properties)
163 : base(properties["ProductCode"], null, UserContexts.None)
164 {
165 this.properties = properties;
166 }
167
168 /// <summary>
169 /// Gets the set of published features for the product.
170 /// </summary>
171 /// <returns>Enumeration of published features for the product.</returns>
172 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
173 /// <remarks><p>
174 /// Because features are not ordered, any new feature has an arbitrary index, meaning
175 /// this property can return features in any order.
176 /// </p><p>
177 /// Win32 MSI API:
178 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumfeatures.asp">MsiEnumFeatures</a>
179 /// </p></remarks>
180 public IEnumerable<FeatureInstallation> Features
181 {
182 get
183 {
184 StringBuilder buf = new StringBuilder(256);
185 for (uint i = 0; ; i++)
186 {
187 uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, buf, null);
188
189 if (ret != 0)
190 {
191 break;
192 }
193
194 yield return new FeatureInstallation(buf.ToString(), this.ProductCode);
195 }
196 }
197 }
198
199 /// <summary>
200 /// Gets the ProductCode (GUID) of the product.
201 /// </summary>
202 public string ProductCode
203 {
204 get { return this.InstallationCode; }
205 }
206
207 /// <summary>
208 /// Gets a value indicating whether this product is installed on the current system.
209 /// </summary>
210 public override bool IsInstalled
211 {
212 get
213 {
214 return (this.State == InstallState.Default);
215 }
216 }
217
218 /// <summary>
219 /// Gets a value indicating whether this product is advertised on the current system.
220 /// </summary>
221 public bool IsAdvertised
222 {
223 get
224 {
225 return (this.State == InstallState.Advertised);
226 }
227 }
228
229 /// <summary>
230 /// Checks whether the product is installed with elevated privileges. An application is called
231 /// a "managed application" if elevated (system) privileges are used to install the application.
232 /// </summary>
233 /// <returns>True if the product is elevated; false otherwise</returns>
234 /// <remarks><p>
235 /// Note that this property does not take into account policies such as AlwaysInstallElevated,
236 /// but verifies that the local system owns the product's registry data.
237 /// </p></remarks>
238 public bool IsElevated
239 {
240 get
241 {
242 bool isElevated;
243 uint ret = NativeMethods.MsiIsProductElevated(this.ProductCode, out isElevated);
244 if (ret != 0)
245 {
246 throw InstallerException.ExceptionFromReturnCode(ret);
247 }
248 return isElevated;
249 }
250 }
251
252 /// <summary>
253 /// Gets the source list of this product installation.
254 /// </summary>
255 public override SourceList SourceList
256 {
257 get
258 {
259 return this.properties == null ? base.SourceList : null;
260 }
261 }
262
263 internal InstallState State
264 {
265 get
266 {
267 if (this.properties != null)
268 {
269 return InstallState.Unknown;
270 }
271 else
272 {
273 int installState = NativeMethods.MsiQueryProductState(this.ProductCode);
274 return (InstallState) installState;
275 }
276 }
277 }
278
279 internal override int InstallationType
280 {
281 get
282 {
283 const int MSICODE_PRODUCT = 0x00000000;
284 return MSICODE_PRODUCT;
285 }
286 }
287
288 /// <summary>
289 /// The support link.
290 /// </summary>
291 public string HelpLink
292 {
293 get
294 {
295 return this["HelpLink"];
296 }
297 }
298
299 /// <summary>
300 /// The support telephone.
301 /// </summary>
302 public string HelpTelephone
303 {
304 get
305 {
306 return this["HelpTelephone"];
307 }
308 }
309
310 /// <summary>
311 /// Date and time the product was installed.
312 /// </summary>
313 public DateTime InstallDate
314 {
315 get
316 {
317 try
318 {
319 return DateTime.ParseExact(
320 this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture);
321 }
322 catch (FormatException)
323 {
324 return DateTime.MinValue;
325 }
326 }
327 }
328
329 /// <summary>
330 /// The installed product name.
331 /// </summary>
332 public string ProductName
333 {
334 get
335 {
336 return this["InstalledProductName"];
337 }
338 }
339
340 /// <summary>
341 /// The installation location.
342 /// </summary>
343 public string InstallLocation
344 {
345 get
346 {
347 return this["InstallLocation"];
348 }
349 }
350
351 /// <summary>
352 /// The installation source.
353 /// </summary>
354 public string InstallSource
355 {
356 get
357 {
358 return this["InstallSource"];
359 }
360 }
361
362 /// <summary>
363 /// The local cached package.
364 /// </summary>
365 public string LocalPackage
366 {
367 get
368 {
369 return this["LocalPackage"];
370 }
371 }
372
373 /// <summary>
374 /// The publisher.
375 /// </summary>
376 public string Publisher
377 {
378 get
379 {
380 return this["Publisher"];
381 }
382 }
383
384 /// <summary>
385 /// URL about information.
386 /// </summary>
387 public Uri UrlInfoAbout
388 {
389 get
390 {
391 string value = this["URLInfoAbout"];
392 if (!String.IsNullOrEmpty(value))
393 {
394 try
395 {
396 return new Uri(value);
397 }
398 catch (UriFormatException) { }
399 }
400
401 return null;
402 }
403 }
404
405 /// <summary>
406 /// The URL update information.
407 /// </summary>
408 public Uri UrlUpdateInfo
409 {
410 get
411 {
412 string value = this["URLUpdateInfo"];
413 if (!String.IsNullOrEmpty(value))
414 {
415 try
416 {
417 return new Uri(value);
418 }
419 catch (UriFormatException) { }
420 }
421
422 return null;
423 }
424 }
425
426 /// <summary>
427 /// The product version.
428 /// </summary>
429 public Version ProductVersion
430 {
431 get
432 {
433 string ver = this["VersionString"];
434 return ProductInstallation.ParseVersion(ver);
435 }
436 }
437
438 /// <summary>
439 /// The product identifier.
440 /// </summary>
441 /// <remarks><p>
442 /// For more information, see
443 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/productid.asp">ProductID</a>
444 /// </p></remarks>
445 public string ProductId
446 {
447 get
448 {
449 return this["ProductID"];
450 }
451 }
452
453 /// <summary>
454 /// The company that is registered to use the product.
455 /// </summary>
456 public string RegCompany
457 {
458 get
459 {
460 return this["RegCompany"];
461 }
462 }
463
464 /// <summary>
465 /// The owner who is registered to use the product.
466 /// </summary>
467 public string RegOwner
468 {
469 get
470 {
471 return this["RegOwner"];
472 }
473 }
474
475 /// <summary>
476 /// Transforms.
477 /// </summary>
478 public string AdvertisedTransforms
479 {
480 get
481 {
482 return this["Transforms"];
483 }
484 }
485
486 /// <summary>
487 /// Product language.
488 /// </summary>
489 public string AdvertisedLanguage
490 {
491 get
492 {
493 return this["Language"];
494 }
495 }
496
497 /// <summary>
498 /// Human readable product name.
499 /// </summary>
500 public string AdvertisedProductName
501 {
502 get
503 {
504 return this["ProductName"];
505 }
506 }
507
508 /// <summary>
509 /// True if the product is advertised per-machine;
510 /// false if it is per-user or not advertised.
511 /// </summary>
512 public bool AdvertisedPerMachine
513 {
514 get
515 {
516 return this["AssignmentType"] == "1";
517 }
518 }
519
520 /// <summary>
521 /// Identifier of the package that a product is installed from.
522 /// </summary>
523 public string AdvertisedPackageCode
524 {
525 get
526 {
527 return this["PackageCode"];
528 }
529 }
530
531 /// <summary>
532 /// Version of the advertised product.
533 /// </summary>
534 public Version AdvertisedVersion
535 {
536 get
537 {
538 string ver = this["Version"];
539 return ProductInstallation.ParseVersion(ver);
540 }
541 }
542
543 /// <summary>
544 /// Primary icon for the package.
545 /// </summary>
546 public string AdvertisedProductIcon
547 {
548 get
549 {
550 return this["ProductIcon"];
551 }
552 }
553
554 /// <summary>
555 /// Name of the installation package for the advertised product.
556 /// </summary>
557 public string AdvertisedPackageName
558 {
559 get
560 {
561 return this["PackageName"];
562 }
563 }
564
565 /// <summary>
566 /// True if the advertised product can be serviced by
567 /// non-administrators without elevation.
568 /// </summary>
569 public bool PrivilegedPatchingAuthorized
570 {
571 get
572 {
573 return this["AuthorizedLUAApp"] == "1";
574 }
575 }
576
577 /// <summary>
578 /// Gets information about an installation of a product.
579 /// </summary>
580 /// <param name="propertyName">Name of the property being retrieved.</param>
581 /// <exception cref="ArgumentOutOfRangeException">An unknown product or property was requested</exception>
582 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
583 /// <remarks><p>
584 /// Win32 MSI APIs:
585 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfo.asp">MsiGetProductInfo</a>,
586 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfoex.asp">MsiGetProductInfoEx</a>
587 /// </p></remarks>
588 public override string this[string propertyName]
589 {
590 get
591 {
592 if (this.properties != null)
593 {
594 string value = null;
595 this.properties.TryGetValue(propertyName, out value);
596 return value;
597 }
598 else
599 {
600 StringBuilder buf = new StringBuilder(40);
601 uint bufSize = (uint) buf.Capacity;
602 uint ret;
603
604 if (this.Context == UserContexts.UserManaged ||
605 this.Context == UserContexts.UserUnmanaged ||
606 this.Context == UserContexts.Machine)
607 {
608 ret = NativeMethods.MsiGetProductInfoEx(
609 this.ProductCode,
610 this.UserSid,
611 this.Context,
612 propertyName,
613 buf,
614 ref bufSize);
615 if (ret == (uint) NativeMethods.Error.MORE_DATA)
616 {
617 buf.Capacity = (int) ++bufSize;
618 ret = NativeMethods.MsiGetProductInfoEx(
619 this.ProductCode,
620 this.UserSid,
621 this.Context,
622 propertyName,
623 buf,
624 ref bufSize);
625 }
626 }
627 else
628 {
629 ret = NativeMethods.MsiGetProductInfo(
630 this.ProductCode,
631 propertyName,
632 buf,
633 ref bufSize);
634 if (ret == (uint) NativeMethods.Error.MORE_DATA)
635 {
636 buf.Capacity = (int) ++bufSize;
637 ret = NativeMethods.MsiGetProductInfo(
638 this.ProductCode,
639 propertyName,
640 buf,
641 ref bufSize);
642 }
643 }
644
645 if (ret != 0)
646 {
647 return null;
648 }
649
650 return buf.ToString();
651 }
652 }
653 }
654
655 /// <summary>
656 /// Gets the installed state for a product feature.
657 /// </summary>
658 /// <param name="feature">The feature being queried; identifier from the
659 /// Feature table</param>
660 /// <returns>Installation state of the feature for the product instance: either
661 /// <see cref="InstallState.Local"/>, <see cref="InstallState.Source"/>,
662 /// or <see cref="InstallState.Advertised"/>.</returns>
663 /// <remarks><p>
664 /// Win32 MSI APIs:
665 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>,
666 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestateex.asp">MsiQueryFeatureStateEx</a>
667 /// </p></remarks>
668 public InstallState GetFeatureState(string feature)
669 {
670 if (this.properties != null)
671 {
672 return InstallState.Unknown;
673 }
674 else
675 {
676 int installState;
677 uint ret = NativeMethods.MsiQueryFeatureStateEx(
678 this.ProductCode,
679 this.UserSid,
680 this.Context,
681 feature,
682 out installState);
683 if (ret != 0)
684 {
685 throw InstallerException.ExceptionFromReturnCode(ret);
686 }
687 return (InstallState) installState;
688 }
689 }
690
691 /// <summary>
692 /// Gets the installed state for a product component.
693 /// </summary>
694 /// <param name="component">The component being queried; GUID of the component
695 /// as found in the ComponentId column of the Component table.</param>
696 /// <returns>Installation state of the component for the product instance: either
697 /// <see cref="InstallState.Local"/> or <see cref="InstallState.Source"/>.</returns>
698 /// <remarks><p>
699 /// Win32 MSI API:
700 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiquerycomponnetstate.asp">MsiQueryComponentState</a>
701 /// </p></remarks>
702 public InstallState GetComponentState(string component)
703 {
704 if (this.properties != null)
705 {
706 return InstallState.Unknown;
707 }
708 else
709 {
710 int installState;
711 uint ret = NativeMethods.MsiQueryComponentState(
712 this.ProductCode,
713 this.UserSid,
714 this.Context,
715 component,
716 out installState);
717 if (ret != 0)
718 {
719 throw InstallerException.ExceptionFromReturnCode(ret);
720 }
721 return (InstallState) installState;
722 }
723 }
724
725 /// <summary>
726 /// Obtains and stores the user information and product ID from an installation wizard.
727 /// </summary>
728 /// <remarks><p>
729 /// This method is typically called by an application during the first run of the application. The application
730 /// first gets the <see cref="ProductInstallation.ProductId"/> or <see cref="ProductInstallation.RegOwner"/>.
731 /// If those properties are missing, the application calls CollectUserInfo.
732 /// CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects
733 /// user information. Upon completion of the sequence, user information is registered. Since this API requires
734 /// an authored user interface, the user interface level should be set to full by calling
735 /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/> as <see cref="InstallUIOptions.Full"/>.
736 /// </p><p>
737 /// The CollectUserInfo method invokes a FirstRun dialog from the product installation database.
738 /// </p><p>
739 /// Win32 MSI API:
740 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicollectuserinfo.asp">MsiCollectUserInfo</a>
741 /// </p></remarks>
742 public void CollectUserInfo()
743 {
744 if (this.properties == null)
745 {
746 uint ret = NativeMethods.MsiCollectUserInfo(this.InstallationCode);
747 if (ret != 0)
748 {
749 throw InstallerException.ExceptionFromReturnCode(ret);
750 }
751 }
752 }
753
754 /// <summary>
755 /// Some products might write some invalid/nonstandard version strings to the registry.
756 /// This method tries to get the best data it can.
757 /// </summary>
758 /// <param name="ver">Version string retrieved from the registry.</param>
759 /// <returns>Version object, or null if the version string is completely invalid.</returns>
760 private static Version ParseVersion(string ver)
761 {
762 if (ver != null)
763 {
764 int dotCount = 0;
765 for (int i = 0; i < ver.Length; i++)
766 {
767 char c = ver[i];
768 if (c == '.') dotCount++;
769 else if (!Char.IsDigit(c))
770 {
771 ver = ver.Substring(0, i);
772 break;
773 }
774 }
775
776 if (ver.Length > 0)
777 {
778 if (dotCount == 0)
779 {
780 ver = ver + ".0";
781 }
782 else if (dotCount > 3)
783 {
784 string[] verSplit = ver.Split('.');
785 ver = String.Join(".", verSplit, 0, 4);
786 }
787
788 try
789 {
790 return new Version(ver);
791 }
792 catch (ArgumentException)
793 {
794 }
795 }
796 }
797
798 return null;
799 }
800 }
801}