File: Commands\Workload\Install\WorkloadInstallerFactory.cs
Web Access
Project: src\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.

#nullable disable

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

namespace Microsoft.DotNet.Cli.Commands.Workload.Install;

internal class WorkloadInstallerFactory
{
    public static IInstaller GetWorkloadInstaller(
        IReporter reporter,
        SdkFeatureBand sdkFeatureBand,
        IWorkloadResolver workloadResolver,
        VerbosityOptions verbosity,
        string userProfileDir,
        bool verifyMsiSignature,
        INuGetPackageDownloader nugetPackageDownloader = null,
        string dotnetDir = null,
        string tempDirPath = null,
        PackageSourceLocation packageSourceLocation = null,
        RestoreActionConfig restoreActionConfig = null,
        bool elevationRequired = true,
        bool shouldLog = true)
    {
        dotnetDir = string.IsNullOrWhiteSpace(dotnetDir) ? Path.GetDirectoryName(Environment.ProcessPath) : dotnetDir;
        var installType = WorkloadInstallType.GetWorkloadInstallType(sdkFeatureBand, dotnetDir);

        if (installType == InstallType.Msi)
        {
#if !TARGET_WINDOWS
            throw new InvalidOperationException(CliCommandStrings.OSDoesNotSupportMsi);
#else
            if (!OperatingSystem.IsWindows())
            {
                throw new InvalidOperationException(CliCommandStrings.OSDoesNotSupportMsi);
            }

            // TODO: should restoreActionConfig be flowed through to the client here as well like it is for the FileBasedInstaller below?
            return NetSdkMsiInstallerClient.Create(
                verifyMsiSignature,
                sdkFeatureBand,
                workloadResolver,
                nugetPackageDownloader,
                verbosity,
                packageSourceLocation,
                reporter,
                tempDirPath,
                shouldLog: shouldLog);
#endif
        }

        if (elevationRequired && !WorkloadFileBasedInstall.IsUserLocal(dotnetDir, sdkFeatureBand.ToString()) && !CanWriteToDotnetRoot(dotnetDir))
        {
            throw new GracefulException(CliCommandStrings.InadequatePermissions, isUserError: false);
        }

        userProfileDir ??= CliFolderPathCalculator.DotnetUserProfileFolderPath;

        var installer = new FileBasedInstaller(
            reporter,
            sdkFeatureBand,
            workloadResolver,
            userProfileDir,
            nugetPackageDownloader,
            dotnetDir: dotnetDir,
            tempDirPath: tempDirPath,
            verbosity: verbosity,
            packageSourceLocation: packageSourceLocation,
            restoreActionConfig: restoreActionConfig);

        // Attach corruption repairer to recover from corrupt workload sets
        if (nugetPackageDownloader is not null &&
            workloadResolver?.GetWorkloadManifestProvider() is SdkDirectoryWorkloadManifestProvider sdkProvider &&
            sdkProvider.CorruptionRepairer is null)
        {
            sdkProvider.CorruptionRepairer = new WorkloadManifestCorruptionRepairer(
                reporter,
                installer,
                workloadResolver,
                sdkFeatureBand,
                dotnetDir,
                userProfileDir,
                nugetPackageDownloader,
                packageSourceLocation,
                verbosity);
        }

        return installer;
    }

    private static bool CanWriteToDotnetRoot(string dotnetDir = null)
    {
        dotnetDir ??= Path.GetDirectoryName(Environment.ProcessPath);
        try
        {
            var testPath = Path.Combine(dotnetDir, "metadata", Path.GetRandomFileName());
            if (Directory.Exists(Path.GetDirectoryName(testPath)))
            {
                using FileStream fs = File.Create(testPath, 1, FileOptions.DeleteOnClose);
            }
            else
            {
                Directory.CreateDirectory(Path.GetDirectoryName(testPath));
            }
            return true;
        }
        catch (UnauthorizedAccessException)
        {
            return false;
        }
    }
}