| File: DependencyProvider.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj (Microsoft.DotNet.Cli.Utils) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #if NET using System.Runtime.Versioning; #endif using Microsoft.Win32; namespace Microsoft.DotNet.Cli.Utils; /// <summary> /// <para> /// Represents an installer dependency provider used to manage reference counts against an installer (MSI). /// A dependency provider is an artificial construct introduced in WiX v3.6 to support reference counting installation /// packages. MSIs that support this include a custom table, WixDependencyProvider, and a registry entry that writes the /// provider key to SOFTWARE\Classes\Installer\Dependencies\{provider key name} under HKLM or HKCU. /// </para> /// <para> /// Installers like chainers register a reference by writing a value under the Dependents subkey of the provider key. When the /// MSI is removed, a custom action is executed to determine if there are any remaining dependents and block the uninstall. /// The check can be bypassed by setting IGNOREDEPENDENCIES=ALL. When a chainer removes the MSI, it first removes its /// dependent entry. If there are no other dependents, it can proceed to remove the MSI, otherwise it should do nothing. /// </para> /// </summary> #if NET [SupportedOSPlatform("windows")] #endif public sealed class DependencyProvider { /// <summary> /// The key name used by Visual Studio 2015 and later to register a dependency. /// </summary> internal const string VisualStudioDependentKeyName = "VS.{AEF703B8-D2CC-4343-915C-F54A30B90937}"; /// <summary> /// The relative path from the <see cref="BaseKey"/> to the Dependencies key. /// </summary> internal const string DependenciesKeyRelativePath = @"SOFTWARE\Classes\Installer\Dependencies"; /// <summary> /// <see langword="true"/> if the dependency provider is associated with a per-machine /// installation; <see langword="false"/> otherwise. /// </summary> public readonly bool AllUsers; /// <summary> /// Returns the root key to use: <see cref="Registry.LocalMachine"/> for per-machine installations or /// <see cref="Registry.CurrentUser"/> for per-user installations. /// </summary> public readonly RegistryKey BaseKey; /// <summary> /// Gets all dependents associated with the provider key. The property always enumerates the /// provider's dependent entries in the registry. /// </summary> public IEnumerable<string> Dependents => GetDependents(); /// <summary> /// The path of the key where the provider's dependents are stored, relative to the <see cref="BaseKey"/>. /// </summary> public readonly string DependentsKeyPath; /// <summary> /// <see langword="true"/> if Visual Studio 2015 or later is registered as a dependent. Visual Studio only /// writes a single entry, regardless of how many instances have taken dependencies. /// </summary> public bool HasVisualStudioDependency => Dependents.Contains(VisualStudioDependentKeyName); /// <summary> /// The name of the dependency provider key used for tracking reference counts. /// </summary> public readonly string ProviderKeyName; /// <summary> /// The product code of the MSI associated with the dependency provider. /// </summary> public string? ProductCode => GetProductCode(); /// <summary> /// The path of the provider key, relative to the <see cref="BaseKey"/>. /// </summary> public readonly string ProviderKeyPath; /// <summary> /// Creates a new <see cref="DependencyProvider"/> instance. /// </summary> /// <param name="providerKeyName">The name of the dependency provider key.</param> /// <param name="allUsers"><see langword="true" /> if the provider belongs to a per-machine installation; /// <see langword="false"/> otherwise.</param> public DependencyProvider(string providerKeyName, bool allUsers = true) { if (providerKeyName is null) { throw new ArgumentNullException(nameof(providerKeyName)); } if (string.IsNullOrWhiteSpace(providerKeyName)) { throw new ArgumentException($"{nameof(providerKeyName)} cannot be empty."); } ProviderKeyName = providerKeyName; AllUsers = allUsers; BaseKey = AllUsers ? Registry.LocalMachine : Registry.CurrentUser; ProviderKeyPath = $@"{DependenciesKeyRelativePath}\{ProviderKeyName}"; DependentsKeyPath = $@"{ProviderKeyPath}\Dependents"; } /// <summary> /// Adds the specified dependent to the provider key. The dependent is stored as a subkey under the Dependents key of /// the provider."/> /// </summary> /// <param name="dependent">The dependent to add.</param> public void AddDependent(string dependent) { if (dependent is null) { throw new ArgumentNullException(nameof(dependent)); } if (string.IsNullOrWhiteSpace(dependent)) { throw new ArgumentException($"{nameof(dependent)} cannot be empty."); } using RegistryKey dependentsKey = BaseKey.CreateSubKey(Path.Combine(DependentsKeyPath, dependent), writable: true); } /// <summary> /// Remove the specified dependent from the provider key. Optionally, if this is the final dependent, /// the provider key can also be removed. This is typically done during an uninstall. /// </summary> /// <param name="dependent">The dependent to remove.</param> /// <param name="removeProvider">When <see langword="true"/>, delete the provider key if the dependent being /// removed is the last dependent.</param> public void RemoveDependent(string dependent, bool removeProvider) { if (dependent is null) { throw new ArgumentNullException(nameof(dependent)); } if (string.IsNullOrWhiteSpace(dependent)) { throw new ArgumentException($"{nameof(dependent)} cannot be empty."); } using RegistryKey? dependentsKey = BaseKey.OpenSubKey(DependentsKeyPath, writable: true); dependentsKey?.DeleteSubKeyTree(dependent); if ((removeProvider) && (Dependents.Count() == 0)) { using RegistryKey? providerKey = BaseKey.OpenSubKey(DependenciesKeyRelativePath, writable: true); providerKey?.DeleteSubKeyTree(ProviderKeyName); } } /// <summary> /// Gets all the dependents associated with the provider key. /// </summary> /// <returns>All dependents of the provider key.</returns> private IEnumerable<string> GetDependents() { using RegistryKey? dependentsKey = BaseKey.OpenSubKey(DependentsKeyPath); return dependentsKey?.GetSubKeyNames() ?? Enumerable.Empty<string>(); } /// <summary> /// Gets the ProductCode associated with this dependency provider. The ProductCode is stored in the default /// value. /// </summary> /// <returns>The ProductCode associated with this dependency provider or <see langword="null"/> if it does not exist.</returns> private string? GetProductCode() { using RegistryKey? providerKey = BaseKey.OpenSubKey(ProviderKeyPath); return providerKey?.GetValue(null) as string ?? null; } public override string ToString() => ProviderKeyName; public static DependencyProvider? GetFromProductCode(string productCode, bool allUsers = true) { var baseKey = allUsers ? Registry.LocalMachine : Registry.CurrentUser; using RegistryKey? dependenciesKey = baseKey.OpenSubKey(DependenciesKeyRelativePath); foreach (var providerKeyName in dependenciesKey?.GetSubKeyNames() ?? []) { using RegistryKey? providerKey = dependenciesKey?.OpenSubKey(providerKeyName); var thisProductCode = providerKey?.GetValue(null) as string ?? null; if (string.Equals(thisProductCode, productCode, StringComparison.OrdinalIgnoreCase)) { return new DependencyProvider(providerKeyName, allUsers); } } return null; } }