File: Commands\Workload\WorkloadInstallDetector.cs
Web Access
Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DotNet.Cli.Commands.Workload.Install.WorkloadInstallRecords;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.NET.Sdk.WorkloadManifestReader;

namespace Microsoft.DotNet.Cli.Commands.Workload;

/// <summary>
///  Read-only, dependency-light detection of whether any workloads are installed for the running
///  SDK's feature band.
///
///  <para>
///  <see cref="WorkloadIntegrityChecker.RunFirstUseCheck"/> only performs an (expensive) repair when
///  installed workloads exist; the resolver/installer plumbing it uses to discover that fact drags in
///  the NuGet engine (and, on Windows, the MSI installer IPC). This type answers the same "are any
///  workloads installed?" question by reading only the filesystem and registry, so it has no NuGet,
///  MSBuild, cryptography, or installer-IPC dependencies and can run in-process under NativeAOT. The
///  repair itself stays on the managed CLI.
///  </para>
/// </summary>
internal static class WorkloadInstallDetector
{
    /// <summary>
    ///  Returns <see langword="true"/> if at least one workload is recorded as installed for the
    ///  current SDK feature band. File-based installs are read from the workload metadata directory;
    ///  MSI-based installs (Windows) are read from the machine registry.
    /// </summary>
    /// <param name="dotnetDir">
    ///  The dotnet root to inspect. Defaults to the directory of the running process.
    /// </param>
    public static bool HasInstalledWorkloadsForCurrentBand(string? dotnetDir = null)
    {
        dotnetDir = string.IsNullOrWhiteSpace(dotnetDir) ? Path.GetDirectoryName(Environment.ProcessPath) : dotnetDir;
        var sdkFeatureBand = new SdkFeatureBand(Product.Version);

        return WorkloadInstallType.GetWorkloadInstallType(sdkFeatureBand, dotnetDir) switch
        {
            InstallType.Msi => HasMsiWorkloadRecords(sdkFeatureBand),
            _ => HasFileBasedWorkloadRecords(dotnetDir, sdkFeatureBand),
        };
    }

    private static bool HasFileBasedWorkloadRecords(string? dotnetDir, SdkFeatureBand sdkFeatureBand)
    {
        // Records live under {workloadRoot}/metadata/workloads, where workloadRoot is the user profile
        // for user-local installs and the dotnet root otherwise. This mirrors the layout used by
        // FileBasedInstaller / FileBasedInstallationRecordRepository.
        if (dotnetDir is null)
        {
            return false;
        }

        return FileBasedWorkloadInstallationRecordRepositoryFactory
            .Create(dotnetDir, sdkFeatureBand, CliFolderPathCalculator.DotnetUserProfileFolderPath)
            .GetInstalledWorkloads(sdkFeatureBand)
            .Any();
    }

    private static bool HasMsiWorkloadRecords(SdkFeatureBand sdkFeatureBand)
    {
        if (!OperatingSystem.IsWindows())
        {
            return false;
        }

        return new ReadOnlyWindowsWorkloadInstallationRecordRepository()
            .GetInstalledWorkloads(sdkFeatureBand)
            .Any();
    }
}