// 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; /// /// A list of sources for an installed product or patch. /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] public class SourceList : ICollection { private Installation installation; private SourceMediaList mediaList; internal SourceList(Installation installation) { this.installation = installation; } /// /// Gets the list of disks registered for the media source of /// the patch or product installation. /// public SourceMediaList MediaList { get { if (this.mediaList == null) { this.mediaList = new SourceMediaList(this.installation); } return this.mediaList; } } /// /// Gets the number of network and URL sources in the list. /// public int Count { get { int count = 0; IEnumerator e = this.GetEnumerator(); while (e.MoveNext()) { count++; } return count; } } /// /// Gets a boolean value indicating whether the list is read-only. /// A SourceList is never read-only. /// /// read-only status of the list public bool IsReadOnly { get { return false; } } /// /// Adds a network or URL source to the source list of the installed product. /// /// Path to the source to be added. This parameter is /// expected to contain only the path without the filename. ///

/// If this method is called with a new source, the installer adds the source /// to the end of the source list. ///

/// If this method is called with a source already existing in the source /// list, it has no effect. ///

/// Win32 MSI APIs: /// MsiSourceListAddSource, /// MsiSourceListAddSourceEx ///

/// public void Add(string item) { if (!this.Contains(item)) { this.Insert(item, 0); } } /// /// Adds or reorders a network or URL source for the product or patch. /// /// Path to the source to be added. This parameter is /// expected to contain only the path without the filename. /// Specifies the priority order in which the source /// will be inserted ///

/// If this method is called with a new source and /// is set to 0, the installer adds the source to the end of the source list. ///

/// If this method is called with a source already existing in the source /// list and is set to 0, the installer retains the /// source's existing index. ///

/// If the method is called with an existing source in the source list /// and is set to a non-zero value, the source is /// removed from its current location in the list and inserted at the position /// specified by Index, before any source that already exists at that position. ///

/// If the method is called with a new source and Index is set to a /// non-zero value, the source is inserted at the position specified by /// , before any source that already exists at /// that position. The index value for all sources in the list after the /// index specified by Index are updated to ensure unique index values and /// the pre-existing order is guaranteed to remain unchanged. ///

/// If is greater than the number of sources /// in the list, the source is placed at the end of the list with an index /// value one larger than any existing source. ///

/// Win32 MSI API: /// MsiSourceListAddSourceEx ///

public void Insert(string item, int index) { if (item == null) { throw new ArgumentNullException("item"); } NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network; uint ret = NativeMethods.MsiSourceListAddSourceEx( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) type | (uint) this.installation.InstallationType, item, (uint) index); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } } /// /// Clears sources of all types: network, url, and media. /// ///

/// Win32 MSI API: /// MsiSourceListClearAll ///

public void Clear() { this.ClearSourceType(NativeMethods.SourceType.Url); this.ClearSourceType(NativeMethods.SourceType.Network); this.MediaList.Clear(); } /// /// Removes all network sources from the list. URL sources are not affected. /// ///

/// Win32 MSI API: /// MsiSourceListClearAllEx ///

public void ClearNetworkSources() { this.ClearSourceType(NativeMethods.SourceType.Network); } /// /// Removes all URL sources from the list. Network sources are not affected. /// ///

/// Win32 MSI API: /// MsiSourceListClearAllEx ///

public void ClearUrlSources() { this.ClearSourceType(NativeMethods.SourceType.Url); } /// /// Checks if the specified source exists in the list. /// /// case-insensitive source to look for /// true if the source exists in the list, false otherwise public bool Contains(string item) { if (String.IsNullOrEmpty(item)) { throw new ArgumentNullException("item"); } foreach (string s in this) { if (s.Equals(item, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } /// /// Copies the network and URL sources from this list into an array. /// /// destination array to be filed /// offset into the destination array where copying begins public void CopyTo(string[] array, int arrayIndex) { foreach (string source in this) { array[arrayIndex++] = source; } } /// /// Removes a network or URL source. /// ///

/// Win32 MSI API: /// MsiSourceListClearSource ///

public bool Remove(string item) { if (String.IsNullOrEmpty(item)) { throw new ArgumentNullException("item"); } NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network; uint ret = NativeMethods.MsiSourceListClearSource( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) type | (uint) this.installation.InstallationType, item); if (ret != 0) { // TODO: Figure out when to return false. throw InstallerException.ExceptionFromReturnCode(ret); } return true; } /// /// Enumerates the network and URL sources in the source list of the patch or product installation. /// ///

/// Win32 MSI API: /// MsiSourceListEnumSources ///

public IEnumerator GetEnumerator() { StringBuilder sourceBuf = new StringBuilder(256); uint sourceBufSize = (uint) sourceBuf.Capacity; for (uint i = 0; true; i++) { uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Network); if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) { break; } else if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } else { yield return sourceBuf.ToString(); } } for (uint i = 0; true; i++) { uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Url); if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) { break; } else if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } else { yield return sourceBuf.ToString(); } } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// Forces the installer to search the source list for a valid /// source the next time a source is required. For example, when the /// installer performs an installation or reinstallation, or when it /// requires the path for a component that is set to run from source. /// ///

/// Win32 MSI APIs: /// MsiSourceListForceResolution, /// MsiSourceListForceResolutionEx ///

public void ForceResolution() { uint ret = NativeMethods.MsiSourceListForceResolutionEx( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) this.installation.InstallationType); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } } /// /// Gets or sets the path relative to the root of the installation media. /// public string MediaPackagePath { get { return this["MediaPackagePath"]; } set { this["MediaPackagePath"] = value; } } /// /// Gets or sets the prompt template that is used when prompting the user /// for installation media. /// public string DiskPrompt { get { return this["DiskPrompt"]; } set { this["DiskPrompt"] = value; } } /// /// Gets or sets the most recently used source location for the product. /// public string LastUsedSource { get { return this["LastUsedSource"]; } set { this["LastUsedSource"] = value; } } /// /// Gets or sets the name of the Windows Installer package or patch package /// on the source. /// public string PackageName { get { return this["PackageName"]; } set { this["PackageName"] = value; } } /// /// Gets the type of the last-used source. /// ///

///

    ///
  • "n" = network location
  • ///
  • "u" = URL location
  • ///
  • "m" = media location
  • ///
  • (empty string) = no last used source
  • ///
///

public string LastUsedType { get { return this["LastUsedType"]; } } /// /// Gets or sets source list information properties of a product or patch installation. /// /// The source list information property name. /// An unknown product, patch, or property was requested ///

/// Win32 MSI API: /// MsiSourceListGetInfo ///

public string this[string property] { get { StringBuilder buf = new StringBuilder(""); uint bufSize = 0; uint ret = NativeMethods.MsiSourceListGetInfo( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) this.installation.InstallationType, property, buf, ref bufSize); if (ret != 0) { if (ret == (uint) NativeMethods.Error.MORE_DATA) { buf.Capacity = (int) ++bufSize; ret = NativeMethods.MsiSourceListGetInfo( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) this.installation.InstallationType, property, buf, ref bufSize); } if (ret != 0) { if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT || ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY) { throw new ArgumentOutOfRangeException("property"); } else { throw InstallerException.ExceptionFromReturnCode(ret); } } } return buf.ToString(); } set { uint ret = NativeMethods.MsiSourceListSetInfo( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) this.installation.InstallationType, property, value); if (ret != 0) { if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT || ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY) { throw new ArgumentOutOfRangeException("property"); } else { throw InstallerException.ExceptionFromReturnCode(ret); } } } } private void ClearSourceType(NativeMethods.SourceType type) { uint ret = NativeMethods.MsiSourceListClearAllEx( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) type | (uint) this.installation.InstallationType); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } } private uint EnumSources(StringBuilder sourceBuf, uint i, NativeMethods.SourceType sourceType) { int enumType = (this.installation.InstallationType | (int) sourceType); uint sourceBufSize = (uint) sourceBuf.Capacity; uint ret = NativeMethods.MsiSourceListEnumSources( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) enumType, i, sourceBuf, ref sourceBufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { sourceBuf.Capacity = (int) ++sourceBufSize; ret = NativeMethods.MsiSourceListEnumSources( this.installation.InstallationCode, this.installation.UserSid, this.installation.Context, (uint) enumType, i, sourceBuf, ref sourceBufSize); } return ret; } } }