// 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. namespace WixToolset.Dtf.WindowsInstaller { using System; using System.Text; using System.Collections.Generic; using System.Globalization; using System.Diagnostics.CodeAnalysis; /// /// Represents a unique instance of a product that /// is either advertised, installed or unknown. /// public class ProductInstallation : Installation { /// /// Gets the set of all products with a specified upgrade code. This method lists the /// currently installed and advertised products that have the specified UpgradeCode /// property in their Property table. /// /// Upgrade code of related products /// Enumeration of product codes ///

/// Win32 MSI API: /// MsiEnumRelatedProducts ///

public static IEnumerable GetRelatedProducts(string upgradeCode) { StringBuilder buf = new StringBuilder(40); for (uint i = 0; true; i++) { uint ret = NativeMethods.MsiEnumRelatedProducts(upgradeCode, 0, i, buf); if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break; if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } yield return new ProductInstallation(buf.ToString()); } } /// /// Enumerates all product installations on the system. /// /// An enumeration of product objects. ///

/// Win32 MSI API: /// MsiEnumProducts, ///

public static IEnumerable AllProducts { get { return GetProducts(null, null, UserContexts.All); } } /// /// Enumerates product installations based on certain criteria. /// /// ProductCode (GUID) of the product instances to be enumerated. Only /// instances of products within the scope of the context specified by the /// and parameters will be /// enumerated. This parameter may be set to null to enumerate all products in the specified /// context. /// Specifies a security identifier (SID) that restricts the context /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts /// enumeration to the current user or any user in the system. The special SID string /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter /// can be set to null to restrict the enumeration scope to the current user. When /// is set to the machine context only, /// must be null. /// Specifies the user context. /// An enumeration of product objects for enumerated product instances. ///

/// Win32 MSI API: /// MsiEnumProductsEx ///

public static IEnumerable GetProducts( string productCode, string userSid, UserContexts context) { StringBuilder buf = new StringBuilder(40); UserContexts targetContext; StringBuilder targetSidBuf = new StringBuilder(40); for (uint i = 0; ; i++) { uint targetSidBufSize = (uint) targetSidBuf.Capacity; uint ret = NativeMethods.MsiEnumProductsEx( productCode, userSid, context, i, buf, out targetContext, targetSidBuf, ref targetSidBufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { targetSidBuf.Capacity = (int) ++targetSidBufSize; ret = NativeMethods.MsiEnumProductsEx( productCode, userSid, context, i, buf, out targetContext, targetSidBuf, ref targetSidBufSize); } if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) { break; } if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } yield return new ProductInstallation( buf.ToString(), targetSidBuf.ToString(), targetContext); } } private IDictionary properties; /// /// Creates a new object for accessing information about a product installation on the current system. /// /// ProductCode (GUID) of the product. ///

/// All available user contexts will be queried. ///

public ProductInstallation(string productCode) : this(productCode, null, UserContexts.All) { } /// /// Creates a new object for accessing information about a product installation on the current system. /// /// ProductCode (GUID) of the product. /// The specific user, when working in a user context. This /// parameter may be null to indicate the current user. The parameter must be null /// when working in a machine context. /// The user context. The calling process must have administrative /// privileges to get information for a product installed for a user other than the /// current user. public ProductInstallation(string productCode, string userSid, UserContexts context) : base(productCode, userSid, context) { if (String.IsNullOrEmpty(productCode)) { throw new ArgumentNullException("productCode"); } } internal ProductInstallation(IDictionary properties) : base(properties["ProductCode"], null, UserContexts.None) { this.properties = properties; } /// /// Gets the set of published features for the product. /// /// Enumeration of published features for the product. /// The installer configuration data is corrupt ///

/// Because features are not ordered, any new feature has an arbitrary index, meaning /// this property can return features in any order. ///

/// Win32 MSI API: /// MsiEnumFeatures ///

public IEnumerable Features { get { StringBuilder buf = new StringBuilder(256); for (uint i = 0; ; i++) { uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, buf, null); if (ret != 0) { break; } yield return new FeatureInstallation(buf.ToString(), this.ProductCode); } } } /// /// Gets the ProductCode (GUID) of the product. /// public string ProductCode { get { return this.InstallationCode; } } /// /// Gets a value indicating whether this product is installed on the current system. /// public override bool IsInstalled { get { return (this.State == InstallState.Default); } } /// /// Gets a value indicating whether this product is advertised on the current system. /// public bool IsAdvertised { get { return (this.State == InstallState.Advertised); } } /// /// Checks whether the product is installed with elevated privileges. An application is called /// a "managed application" if elevated (system) privileges are used to install the application. /// /// True if the product is elevated; false otherwise ///

/// Note that this property does not take into account policies such as AlwaysInstallElevated, /// but verifies that the local system owns the product's registry data. ///

public bool IsElevated { get { bool isElevated; uint ret = NativeMethods.MsiIsProductElevated(this.ProductCode, out isElevated); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return isElevated; } } /// /// Gets the source list of this product installation. /// public override SourceList SourceList { get { return this.properties == null ? base.SourceList : null; } } internal InstallState State { get { if (this.properties != null) { return InstallState.Unknown; } else { int installState = NativeMethods.MsiQueryProductState(this.ProductCode); return (InstallState) installState; } } } internal override int InstallationType { get { const int MSICODE_PRODUCT = 0x00000000; return MSICODE_PRODUCT; } } /// /// The support link. /// public string HelpLink { get { return this["HelpLink"]; } } /// /// The support telephone. /// public string HelpTelephone { get { return this["HelpTelephone"]; } } /// /// Date and time the product was installed. /// public DateTime InstallDate { get { try { return DateTime.ParseExact( this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture); } catch (FormatException) { return DateTime.MinValue; } } } /// /// The installed product name. /// public string ProductName { get { return this["InstalledProductName"]; } } /// /// The installation location. /// public string InstallLocation { get { return this["InstallLocation"]; } } /// /// The installation source. /// public string InstallSource { get { return this["InstallSource"]; } } /// /// The local cached package. /// public string LocalPackage { get { return this["LocalPackage"]; } } /// /// The publisher. /// public string Publisher { get { return this["Publisher"]; } } /// /// URL about information. /// public Uri UrlInfoAbout { get { string value = this["URLInfoAbout"]; if (!String.IsNullOrEmpty(value)) { try { return new Uri(value); } catch (UriFormatException) { } } return null; } } /// /// The URL update information. /// public Uri UrlUpdateInfo { get { string value = this["URLUpdateInfo"]; if (!String.IsNullOrEmpty(value)) { try { return new Uri(value); } catch (UriFormatException) { } } return null; } } /// /// The product version. /// public Version ProductVersion { get { string ver = this["VersionString"]; return ProductInstallation.ParseVersion(ver); } } /// /// The product identifier. /// ///

/// For more information, see /// ProductID ///

public string ProductId { get { return this["ProductID"]; } } /// /// The company that is registered to use the product. /// public string RegCompany { get { return this["RegCompany"]; } } /// /// The owner who is registered to use the product. /// public string RegOwner { get { return this["RegOwner"]; } } /// /// Transforms. /// public string AdvertisedTransforms { get { return this["Transforms"]; } } /// /// Product language. /// public string AdvertisedLanguage { get { return this["Language"]; } } /// /// Human readable product name. /// public string AdvertisedProductName { get { return this["ProductName"]; } } /// /// True if the product is advertised per-machine; /// false if it is per-user or not advertised. /// public bool AdvertisedPerMachine { get { return this["AssignmentType"] == "1"; } } /// /// Identifier of the package that a product is installed from. /// public string AdvertisedPackageCode { get { return this["PackageCode"]; } } /// /// Version of the advertised product. /// public Version AdvertisedVersion { get { string ver = this["Version"]; return ProductInstallation.ParseVersion(ver); } } /// /// Primary icon for the package. /// public string AdvertisedProductIcon { get { return this["ProductIcon"]; } } /// /// Name of the installation package for the advertised product. /// public string AdvertisedPackageName { get { return this["PackageName"]; } } /// /// True if the advertised product can be serviced by /// non-administrators without elevation. /// public bool PrivilegedPatchingAuthorized { get { return this["AuthorizedLUAApp"] == "1"; } } /// /// Gets information about an installation of a product. /// /// Name of the property being retrieved. /// An unknown product or property was requested /// The installer configuration data is corrupt ///

/// Win32 MSI APIs: /// MsiGetProductInfo, /// MsiGetProductInfoEx ///

public override string this[string propertyName] { get { if (this.properties != null) { string value = null; this.properties.TryGetValue(propertyName, out value); return value; } else { StringBuilder buf = new StringBuilder(40); uint bufSize = (uint) buf.Capacity; uint ret; if (this.Context == UserContexts.UserManaged || this.Context == UserContexts.UserUnmanaged || this.Context == UserContexts.Machine) { ret = NativeMethods.MsiGetProductInfoEx( this.ProductCode, this.UserSid, this.Context, propertyName, buf, ref bufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { buf.Capacity = (int) ++bufSize; ret = NativeMethods.MsiGetProductInfoEx( this.ProductCode, this.UserSid, this.Context, propertyName, buf, ref bufSize); } } else { ret = NativeMethods.MsiGetProductInfo( this.ProductCode, propertyName, buf, ref bufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { buf.Capacity = (int) ++bufSize; ret = NativeMethods.MsiGetProductInfo( this.ProductCode, propertyName, buf, ref bufSize); } } if (ret != 0) { return null; } return buf.ToString(); } } } /// /// Gets the installed state for a product feature. /// /// The feature being queried; identifier from the /// Feature table /// Installation state of the feature for the product instance: either /// , , /// or . ///

/// Win32 MSI APIs: /// MsiQueryFeatureState, /// MsiQueryFeatureStateEx ///

public InstallState GetFeatureState(string feature) { if (this.properties != null) { return InstallState.Unknown; } else { int installState; uint ret = NativeMethods.MsiQueryFeatureStateEx( this.ProductCode, this.UserSid, this.Context, feature, out installState); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return (InstallState) installState; } } /// /// Gets the installed state for a product component. /// /// The component being queried; GUID of the component /// as found in the ComponentId column of the Component table. /// Installation state of the component for the product instance: either /// or . ///

/// Win32 MSI API: /// MsiQueryComponentState ///

public InstallState GetComponentState(string component) { if (this.properties != null) { return InstallState.Unknown; } else { int installState; uint ret = NativeMethods.MsiQueryComponentState( this.ProductCode, this.UserSid, this.Context, component, out installState); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return (InstallState) installState; } } /// /// Obtains and stores the user information and product ID from an installation wizard. /// ///

/// This method is typically called by an application during the first run of the application. The application /// first gets the or . /// If those properties are missing, the application calls CollectUserInfo. /// CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects /// user information. Upon completion of the sequence, user information is registered. Since this API requires /// an authored user interface, the user interface level should be set to full by calling /// as . ///

/// The CollectUserInfo method invokes a FirstRun dialog from the product installation database. ///

/// Win32 MSI API: /// MsiCollectUserInfo ///

public void CollectUserInfo() { if (this.properties == null) { uint ret = NativeMethods.MsiCollectUserInfo(this.InstallationCode); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } } } /// /// Some products might write some invalid/nonstandard version strings to the registry. /// This method tries to get the best data it can. /// /// Version string retrieved from the registry. /// Version object, or null if the version string is completely invalid. private static Version ParseVersion(string ver) { if (ver != null) { int dotCount = 0; for (int i = 0; i < ver.Length; i++) { char c = ver[i]; if (c == '.') dotCount++; else if (!Char.IsDigit(c)) { ver = ver.Substring(0, i); break; } } if (ver.Length > 0) { if (dotCount == 0) { ver = ver + ".0"; } else if (dotCount > 3) { string[] verSplit = ver.Split('.'); ver = String.Join(".", verSplit, 0, 4); } try { return new Version(ver); } catch (ArgumentException) { } } } return null; } } }