File: Commands\Workload\InstallingWorkloadCommand.cs
Web Access
Project: ..\..\..\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 System.CommandLine;
using Microsoft.Deployment.DotNet.Releases;
using Microsoft.DotNet.Cli.Commands.Workload.Install;
using Microsoft.DotNet.Cli.Commands.Workload.List;
using Microsoft.DotNet.Cli.Commands.Workload.Search;
using Microsoft.DotNet.Cli.Commands.Workload.Update;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using NuGet.Versioning;
using Command = System.CommandLine.Command;
 
namespace Microsoft.DotNet.Cli.Commands.Workload;
 
internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
{
    protected readonly string[] _arguments;
    protected readonly bool _printDownloadLinkOnly;
    protected readonly string _fromCacheOption;
    protected readonly bool _includePreviews;
    protected readonly string _downloadToCacheOption;
    protected readonly string _dotnetPath;
    protected readonly string _userProfileDir;
    protected readonly string _workloadRootDir;
    protected readonly bool _checkIfManifestExist;
    protected readonly ReleaseVersion _sdkVersion;
    protected readonly SdkFeatureBand _sdkFeatureBand;
    protected readonly ReleaseVersion _targetSdkVersion;
    protected readonly string _fromRollbackDefinition;
    protected int _fromHistorySpecified;
    protected bool _historyManifestOnlyOption;
    protected IEnumerable<string> _workloadSetVersionFromCommandLine;
    protected string _globalJsonPath;
    protected string _workloadSetVersionFromGlobalJson;
    protected readonly PackageSourceLocation _packageSourceLocation;
    protected readonly IWorkloadResolverFactory _workloadResolverFactory;
    protected IWorkloadResolver _workloadResolver;
    protected readonly IInstaller _workloadInstallerFromConstructor;
    protected readonly IWorkloadManifestUpdater _workloadManifestUpdaterFromConstructor;
    protected IInstaller _workloadInstaller;
    protected IWorkloadManifestUpdater _workloadManifestUpdater;
    protected bool? _shouldUseWorkloadSets;
    private WorkloadHistoryState _workloadHistoryRecord;
 
    protected bool UseRollback => !string.IsNullOrWhiteSpace(_fromRollbackDefinition);
    protected bool FromHistory => _fromHistorySpecified != 0;
    protected bool SpecifiedWorkloadSetVersionOnCommandLine => _workloadSetVersionFromCommandLine?.Any() == true;
    protected bool SpecifiedWorkloadSetVersionInGlobalJson => !string.IsNullOrWhiteSpace(_workloadSetVersionFromGlobalJson);
    protected WorkloadHistoryState _WorkloadHistoryRecord
    {
        get
        {
            if (_workloadHistoryRecord is null && FromHistory)
            {
                var workloadHistoryRecords = _workloadInstaller.GetWorkloadHistoryRecords(_sdkFeatureBand.ToString()).OrderBy(r => r.TimeStarted).ToList();
                if (workloadHistoryRecords.Count == 0)
                {
                    throw new GracefulException(CliCommandStrings.NoWorkloadHistoryRecords, isUserError: true);
                }
 
                var displayRecords = WorkloadHistoryDisplay.ProcessWorkloadHistoryRecords(workloadHistoryRecords, out _);
 
                if (_fromHistorySpecified < 1 || _fromHistorySpecified > displayRecords.Count)
                {
                    throw new GracefulException(CliCommandStrings.WorkloadHistoryRecordInvalidIdValue, isUserError: true);
                }
 
                _workloadHistoryRecord = displayRecords[_fromHistorySpecified - 1].HistoryState;
            }
 
            return _workloadHistoryRecord;
        }
    }
 
    public InstallingWorkloadCommand(
        ParseResult parseResult,
        IReporter reporter,
        IWorkloadResolverFactory workloadResolverFactory,
        IInstaller workloadInstaller,
        INuGetPackageDownloader nugetPackageDownloader,
        IWorkloadManifestUpdater workloadManifestUpdater,
        string tempDirPath,
        Option<VerbosityOptions> verbosityOptions = null,
        bool? shouldUseWorkloadSetsFromGlobalJson = null)
        : base(parseResult, reporter: reporter, tempDirPath: tempDirPath, nugetPackageDownloader: nugetPackageDownloader, verbosityOptions: verbosityOptions)
    {
        _arguments = parseResult.GetArguments();
        _printDownloadLinkOnly = parseResult.GetValue(InstallingWorkloadCommandParser.PrintDownloadLinkOnlyOption);
        _fromCacheOption = parseResult.GetValue(InstallingWorkloadCommandParser.FromCacheOption);
        _includePreviews = parseResult.GetValue(InstallingWorkloadCommandParser.IncludePreviewOption);
        _downloadToCacheOption = parseResult.GetValue(InstallingWorkloadCommandParser.DownloadToCacheOption);
 
        _fromRollbackDefinition = parseResult.GetValue(InstallingWorkloadCommandParser.FromRollbackFileOption);
        _workloadSetVersionFromCommandLine = parseResult.GetValue(InstallingWorkloadCommandParser.WorkloadSetVersionOption);
 
        var configOption = parseResult.GetValue(InstallingWorkloadCommandParser.ConfigOption);
        var sourceOption = parseResult.GetValue(InstallingWorkloadCommandParser.SourceOption);
        _packageSourceLocation = string.IsNullOrEmpty(configOption) && (sourceOption == null || !sourceOption.Any()) ? null :
            new PackageSourceLocation(string.IsNullOrEmpty(configOption) ? null : new FilePath(configOption), sourceFeedOverrides: sourceOption);
 
        _workloadResolverFactory = workloadResolverFactory ?? new WorkloadResolverFactory();
 
        if (!string.IsNullOrEmpty(parseResult.GetValue(InstallingWorkloadCommandParser.VersionOption)))
        {
            //  Specifying a different SDK version to operate on is only supported for --print-download-link-only and --download-to-cache
            if (_printDownloadLinkOnly || !string.IsNullOrEmpty(_downloadToCacheOption))
            {
                _targetSdkVersion = new ReleaseVersion(parseResult.GetValue(InstallingWorkloadCommandParser.VersionOption));
            }
            else
            {
                throw new GracefulException(CliCommandStrings.SdkVersionOptionNotSupported);
            }
        }
 
        var creationResult = _workloadResolverFactory.Create();
 
        _dotnetPath = creationResult.DotnetPath;
        _userProfileDir = creationResult.UserProfileDir;
        _sdkFeatureBand = new SdkFeatureBand(creationResult.SdkVersion);
        _workloadRootDir = WorkloadFileBasedInstall.IsUserLocal(_dotnetPath, _sdkFeatureBand.ToString()) ? _userProfileDir : _dotnetPath;
        _sdkVersion = creationResult.SdkVersion;
        _workloadResolver = creationResult.WorkloadResolver;
        _targetSdkVersion ??= _sdkVersion;
 
        _workloadInstallerFromConstructor = workloadInstaller;
        _workloadManifestUpdaterFromConstructor = workloadManifestUpdater;
 
        _globalJsonPath = SdkDirectoryWorkloadManifestProvider.GetGlobalJsonPath(Environment.CurrentDirectory);
        _workloadSetVersionFromGlobalJson = SdkDirectoryWorkloadManifestProvider.GlobalJsonReader.GetWorkloadVersionFromGlobalJson(_globalJsonPath, out _shouldUseWorkloadSets);
        _shouldUseWorkloadSets = shouldUseWorkloadSetsFromGlobalJson ?? _shouldUseWorkloadSets;
 
        if (SpecifiedWorkloadSetVersionInGlobalJson && (SpecifiedWorkloadSetVersionOnCommandLine || UseRollback || FromHistory))
        {
            throw new GracefulException(string.Format(CliCommandStrings.CannotSpecifyVersionOnCommandLineAndInGlobalJson, _globalJsonPath), isUserError: true);
        }
        else if (SpecifiedWorkloadSetVersionOnCommandLine && UseRollback)
        {
            throw new GracefulException(string.Format(CliCommandStrings.CannotCombineOptions,
                InstallingWorkloadCommandParser.FromRollbackFileOption.Name,
                InstallingWorkloadCommandParser.WorkloadSetVersionOption.Name), isUserError: true);
        }
        else if (SpecifiedWorkloadSetVersionOnCommandLine && FromHistory)
        {
            throw new GracefulException(string.Format(CliCommandStrings.CannotCombineOptions,
                InstallingWorkloadCommandParser.WorkloadSetVersionOption.Name,
                WorkloadUpdateCommandParser.FromHistoryOption.Name), isUserError: true);
        }
        else if (_shouldUseWorkloadSets == true && (UseRollback || FromHistory && _WorkloadHistoryRecord.WorkloadSetVersion is null))
        {
            throw new GracefulException(CliCommandStrings.SpecifiedWorkloadVersionAndSpecificNonWorkloadVersion, isUserError: true);
        }
        else if (_shouldUseWorkloadSets == false && (SpecifiedWorkloadSetVersionInGlobalJson || SpecifiedWorkloadSetVersionOnCommandLine || FromHistory && _WorkloadHistoryRecord.WorkloadSetVersion is not null))
        {
            throw new GracefulException(CliCommandStrings.SpecifiedNoWorkloadVersionAndSpecificWorkloadVersion, isUserError: true);
        }
 
        //  At this point, at most one of SpecifiedWorkloadSetVersionOnCommandLine, UseRollback, FromHistory, and SpecifiedWorkloadSetVersionInGlobalJson is true
    }
 
    protected static Dictionary<string, string> GetInstallStateContents(IEnumerable<ManifestVersionUpdate> manifestVersionUpdates) =>
        WorkloadSet.FromManifests(
                manifestVersionUpdates.Select(update => new WorkloadManifestInfo(update.ManifestId.ToString(), update.NewVersion.ToString(), /* We don't actually use the directory here */ string.Empty, update.NewFeatureBand))
                ).ToDictionaryForJson();
 
    InstallStateContents GetCurrentInstallState()
    {
        string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _workloadRootDir), "default.json");
        return InstallStateContents.FromPath(path);
    }
 
    public static bool ShouldUseWorkloadSetMode(SdkFeatureBand sdkFeatureBand, string dotnetDir)
    {
        return WorkloadManifestUpdater.ShouldUseWorkloadSetMode(sdkFeatureBand, dotnetDir);
    }
 
    protected void UpdateWorkloadManifests(WorkloadHistoryRecorder recorder, ITransactionContext context, DirectoryPath? offlineCache)
    {
        var shouldUseWorkloadSetsPerInstallState = ShouldUseWorkloadSetMode(_sdkFeatureBand, _workloadRootDir);
        var updateToLatestWorkloadSet = _shouldUseWorkloadSets ?? shouldUseWorkloadSetsPerInstallState && !SpecifiedWorkloadSetVersionInGlobalJson;
        if (FromHistory && !string.IsNullOrWhiteSpace(_WorkloadHistoryRecord.WorkloadSetVersion))
        {
            // This is essentially the same as updating to a specific workload set version, and we're now past the error check,
            // so we can just use the same code path.
            _workloadSetVersionFromCommandLine = [_WorkloadHistoryRecord.WorkloadSetVersion];
        }
        else if ((UseRollback || FromHistory) && updateToLatestWorkloadSet)
        {
            // Rollback files are only for loose manifests. Update the mode to be loose manifests.
            Reporter.WriteLine(CliCommandStrings.UpdateFromRollbackSwitchesModeToLooseManifests);
            _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, false);
            updateToLatestWorkloadSet = false;
        }
 
        string resolvedWorkloadSetVersion = _workloadSetVersionFromGlobalJson;
        if (SpecifiedWorkloadSetVersionInGlobalJson && recorder is not null)
        {
            recorder.HistoryRecord.GlobalJsonVersion = _workloadSetVersionFromGlobalJson;
        }
 
        if (SpecifiedWorkloadSetVersionOnCommandLine)
        {
            updateToLatestWorkloadSet = false;
 
            //  If a workload set version is specified, then switch to workload set update mode
            //  Check to make sure the value needs to be changed, as updating triggers a UAC prompt
            //  for MSI-based installs.
            if (!ShouldUseWorkloadSetMode(_sdkFeatureBand, _workloadRootDir))
            {
                _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, true);
            }
 
            if (_workloadSetVersionFromCommandLine.Any(v => v.Contains('@')))
            {
                var versions = WorkloadSearchVersionsCommand.FindBestWorkloadSetsFromComponents(
                    _sdkFeatureBand,
                    _workloadInstaller is not NetSdkMsiInstallerClient ? _workloadInstaller : null,
                    _sdkFeatureBand.IsPrerelease,
                    PackageDownloader,
                    _workloadSetVersionFromCommandLine,
                    _workloadResolver,
                    numberOfWorkloadSetsToTake: 1);
 
                if (versions is null)
                {
                    return;
                }
                else if (!versions.Any())
                {
                    Reporter.WriteLine(CliCommandStrings.NoWorkloadUpdateFound);
                    return;
                }
                else
                {
                    resolvedWorkloadSetVersion = versions.Single();
                }
            }
            else
            {
                resolvedWorkloadSetVersion = _workloadSetVersionFromCommandLine.Single();
            }
        }
 
        if (string.IsNullOrWhiteSpace(resolvedWorkloadSetVersion) && !UseRollback && !FromHistory)
        {
            _workloadManifestUpdater.UpdateAdvertisingManifestsAsync(_includePreviews, updateToLatestWorkloadSet, offlineCache).Wait();
            if (updateToLatestWorkloadSet)
            {
                resolvedWorkloadSetVersion = _workloadManifestUpdater.GetAdvertisedWorkloadSetVersion();
                var currentWorkloadVersionInfo = _workloadResolver.GetWorkloadVersion();
                if (resolvedWorkloadSetVersion != null && currentWorkloadVersionInfo.IsInstalled && !currentWorkloadVersionInfo.WorkloadSetsEnabledWithoutWorkloadSet)
                {
                    var currentPackageVersion = WorkloadSetVersion.ToWorkloadSetPackageVersion(currentWorkloadVersionInfo.Version, out var currentWorkloadSetSdkFeatureBand);
                    var advertisedPackageVersion = WorkloadSetVersion.ToWorkloadSetPackageVersion(resolvedWorkloadSetVersion, out var advertisedWorkloadSetSdkFeatureBand);
 
                    if (currentWorkloadSetSdkFeatureBand > advertisedWorkloadSetSdkFeatureBand ||
                        new NuGetVersion(currentPackageVersion) >= new NuGetVersion(advertisedPackageVersion))
                    {
                        resolvedWorkloadSetVersion = null;
                    }
                }
            }
        }
 
        if (updateToLatestWorkloadSet && resolvedWorkloadSetVersion == null)
        {
            Reporter.WriteLine(CliCommandStrings.NoWorkloadUpdateFound);
            return;
        }
 
        IEnumerable<ManifestVersionUpdate> manifestsToUpdate =
            resolvedWorkloadSetVersion != null ? InstallWorkloadSet(context, resolvedWorkloadSetVersion) :
                                   UseRollback ? _workloadManifestUpdater.CalculateManifestRollbacks(_fromRollbackDefinition, recorder) :
                                   FromHistory ? _workloadManifestUpdater.CalculateManifestUpdatesFromHistory(_WorkloadHistoryRecord) :
                                                 _workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate);
 
        InstallStateContents oldInstallState = GetCurrentInstallState();
 
        context.Run(
            action: () =>
            {
                foreach (var manifestUpdate in manifestsToUpdate)
                {
                    _workloadInstaller.InstallWorkloadManifest(manifestUpdate, context, offlineCache);
                }
 
                if (!SpecifiedWorkloadSetVersionInGlobalJson)
                {
                    if (UseRollback || FromHistory && string.IsNullOrWhiteSpace(_WorkloadHistoryRecord.WorkloadSetVersion))
                    {
                        _workloadInstaller.SaveInstallStateManifestVersions(_sdkFeatureBand, GetInstallStateContents(manifestsToUpdate));
                        _workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, null);
                    }
                    else if (SpecifiedWorkloadSetVersionOnCommandLine)
                    {
                        _workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, resolvedWorkloadSetVersion);
                    }
                    else if (this is WorkloadUpdateCommand)
                    {
                        //  For workload updates, if you don't specify a rollback file, or a workload version then we should update to a new version of the manifests or workload set, and
                        //  should remove the install state that pins to the other version
                        _workloadInstaller.RemoveManifestsFromInstallState(_sdkFeatureBand);
                        _workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, null);
                    }
                }
 
                _workloadResolver.RefreshWorkloadManifests();
 
                if (_workloadSetVersionFromGlobalJson != null)
                {
                    //  Record GC Root for this global.json file
                    _workloadInstaller.RecordWorkloadSetInGlobalJson(_sdkFeatureBand, _globalJsonPath, _workloadSetVersionFromGlobalJson);
                }
            },
            rollback: () =>
            {
                //  Reset install state
                var currentInstallState = GetCurrentInstallState();
                if (currentInstallState.UseWorkloadSets != oldInstallState.UseWorkloadSets)
                {
                    _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, oldInstallState.UseWorkloadSets);
                }
 
                if (currentInstallState.Manifests == null && oldInstallState.Manifests != null ||
                    currentInstallState.Manifests != null && oldInstallState.Manifests == null ||
                    currentInstallState.Manifests != null && oldInstallState.Manifests != null &&
                     (currentInstallState.Manifests.Count != oldInstallState.Manifests.Count ||
                     !currentInstallState.Manifests.All(m => oldInstallState.Manifests.TryGetValue(m.Key, out var val) && val.Equals(m.Value))))
                {
                    _workloadInstaller.SaveInstallStateManifestVersions(_sdkFeatureBand, oldInstallState.Manifests);
                }
 
                if (currentInstallState.WorkloadVersion != oldInstallState.WorkloadVersion)
                {
                    _workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, oldInstallState.WorkloadVersion);
                }
 
                //  We will refresh the workload manifests to make sure that the resolver has the updated state after the rollback
                _workloadResolver.RefreshWorkloadManifests();
            });
    }
 
    private IEnumerable<ManifestVersionUpdate> InstallWorkloadSet(ITransactionContext context, string workloadSetVersion)
    {
        Reporter.WriteLine(string.Format(CliCommandStrings.NewWorkloadSet, workloadSetVersion));
        var workloadSet = _workloadInstaller.InstallWorkloadSet(context, workloadSetVersion);
 
        return workloadSet is null ? [] : _workloadManifestUpdater.CalculateManifestUpdatesForWorkloadSet(workloadSet);
    }
 
    protected async Task<List<WorkloadDownload>> GetDownloads(IEnumerable<WorkloadId> workloadIds, bool skipManifestUpdate, bool includePreview, string downloadFolder = null,
        IReporter reporter = null, INuGetPackageDownloader packageDownloader = null)
    {
        reporter ??= Reporter;
        packageDownloader ??= PackageDownloader;
 
        List<WorkloadDownload> ret = [];
        DirectoryPath? tempPath = null;
        try
        {
            if (!skipManifestUpdate)
            {
                DirectoryPath folderForManifestDownloads;
                tempPath = new DirectoryPath(Path.Combine(TempDirectoryPath, "dotnet-manifest-extraction"));
                string extractedManifestsPath = Path.Combine(tempPath.Value.Value, "manifests");
 
                if (downloadFolder != null)
                {
                    folderForManifestDownloads = new DirectoryPath(downloadFolder);
                }
                else
                {
                    folderForManifestDownloads = tempPath.Value;
                }
 
                var manifestDownloads = await _workloadManifestUpdater.GetManifestPackageDownloadsAsync(includePreview, new SdkFeatureBand(_targetSdkVersion), _sdkFeatureBand);
 
                if (!manifestDownloads.Any())
                {
                    reporter.WriteLine(CliCommandStrings.SkippingManifestUpdate);
                }
 
                foreach (var download in manifestDownloads)
                {
                    //  Add package to the list of downloads
                    ret.Add(download);
 
                    //  Download package
                    var downloadedPackagePath = await packageDownloader.DownloadPackageAsync(new PackageId(download.NuGetPackageId), new NuGetVersion(download.NuGetPackageVersion),
                        _packageSourceLocation, downloadFolder: folderForManifestDownloads);
 
                    //  Extract manifest from package
                    await _workloadInstaller.ExtractManifestAsync(downloadedPackagePath, Path.Combine(extractedManifestsPath, download.Id));
                }
 
                //  Use updated, extracted manifests to resolve packs
                var overlayProvider = new TempDirectoryWorkloadManifestProvider(extractedManifestsPath, _sdkFeatureBand.ToString());
 
                var newResolver = _workloadResolver.CreateOverlayResolver(overlayProvider);
                _workloadInstaller.ReplaceWorkloadResolver(newResolver);
            }
 
            var packDownloads = _workloadInstaller.GetDownloads(workloadIds, _sdkFeatureBand, false);
            ret.AddRange(packDownloads);
 
            if (downloadFolder != null)
            {
                DirectoryPath downloadFolderDirectoryPath = new(downloadFolder);
                foreach (var packDownload in packDownloads)
                {
                    reporter.WriteLine(string.Format(CliCommandStrings.DownloadingPackToCacheMessage, packDownload.NuGetPackageId, packDownload.NuGetPackageVersion, downloadFolder));
 
                    await packageDownloader.DownloadPackageAsync(new PackageId(packDownload.NuGetPackageId), new NuGetVersion(packDownload.NuGetPackageVersion),
                        _packageSourceLocation, downloadFolder: downloadFolderDirectoryPath);
                }
            }
        }
        finally
        {
            if (tempPath != null && Directory.Exists(tempPath.Value.Value))
            {
                Directory.Delete(tempPath.Value.Value, true);
            }
        }
 
        return ret;
    }
 
    protected IEnumerable<WorkloadId> GetInstalledWorkloads(bool fromPreviousSdk)
    {
        if (fromPreviousSdk)
        {
            var priorFeatureBands = _workloadInstaller.GetWorkloadInstallationRecordRepository().GetFeatureBandsWithInstallationRecords()
                .Where(featureBand => featureBand.CompareTo(_sdkFeatureBand) < 0);
            if (priorFeatureBands.Any())
            {
                var maxPriorFeatureBand = priorFeatureBands.Max();
                return _workloadInstaller.GetWorkloadInstallationRecordRepository().GetInstalledWorkloads(maxPriorFeatureBand);
            }
            return [];
        }
        else
        {
            var workloads = _workloadInstaller.GetWorkloadInstallationRecordRepository().GetInstalledWorkloads(_sdkFeatureBand);
 
            return workloads ?? [];
        }
    }
 
    protected IEnumerable<WorkloadId> WriteSDKInstallRecordsForVSWorkloads(IEnumerable<WorkloadId> workloadsWithExistingInstallRecords)
    {
#if !DOT_NET_BUILD_FROM_SOURCE
        if (OperatingSystem.IsWindows())
        {
            return VisualStudioWorkloads.WriteSDKInstallRecordsForVSWorkloads(_workloadInstaller, _workloadResolver, workloadsWithExistingInstallRecords, Reporter);
        }
#endif
        return workloadsWithExistingInstallRecords;
    }
}
 
internal static class InstallingWorkloadCommandParser
{
    public static readonly Option<IEnumerable<string>> WorkloadSetVersionOption = new("--version")
    {
        Description = CliCommandStrings.WorkloadSetVersionOptionDescription,
        AllowMultipleArgumentsPerToken = true
    };
 
    public static readonly Option<bool> PrintDownloadLinkOnlyOption = new("--print-download-link-only")
    {
        Description = CliCommandStrings.PrintDownloadLinkOnlyDescription,
        Hidden = true
    };
 
    public static readonly Option<string> FromCacheOption = new("--from-cache")
    {
        Description = CliCommandStrings.FromCacheOptionDescription,
        HelpName = CliCommandStrings.FromCacheOptionArgumentName,
        Hidden = true
    };
 
    public static readonly Option<bool> IncludePreviewOption =
    new("--include-previews")
    {
        Description = CliCommandStrings.IncludePreviewOptionDescription
    };
 
    public static readonly Option<string> DownloadToCacheOption = new("--download-to-cache")
    {
        Description = CliCommandStrings.DownloadToCacheOptionDescription,
        HelpName = CliCommandStrings.DownloadToCacheOptionArgumentName,
        Hidden = true
    };
 
    public static readonly Option<string> VersionOption = new("--sdk-version")
    {
        Description = CliCommandStrings.WorkloadInstallVersionOptionDescription,
        HelpName = CliCommandStrings.WorkloadInstallVersionOptionName,
        Hidden = true
    };
 
    public static readonly Option<string> FromRollbackFileOption = new("--from-rollback-file")
    {
        Description = CliCommandStrings.FromRollbackDefinitionOptionDescription,
        Hidden = true
    };
 
    public static readonly Option<string> ConfigOption = new("--configfile")
    {
        Description = CliCommandStrings.WorkloadInstallConfigFileOptionDescription,
        HelpName = CliCommandStrings.WorkloadInstallConfigFileOptionName
    };
 
    public static readonly Option<string[]> SourceOption = new Option<string[]>("--source", "-s")
    {
        Description = CliCommandStrings.WorkloadInstallSourceOptionDescription,
        HelpName = CliCommandStrings.WorkloadInstallSourceOptionName
    }.AllowSingleArgPerToken();
 
    internal static void AddWorkloadInstallCommandOptions(Command command)
    {
        command.Options.Add(VersionOption);
        command.Options.Add(ConfigOption);
        command.Options.Add(SourceOption);
        command.Options.Add(PrintDownloadLinkOnlyOption);
        command.Options.Add(FromCacheOption);
        command.Options.Add(DownloadToCacheOption);
        command.Options.Add(IncludePreviewOption);
        command.Options.Add(FromRollbackFileOption);
    }
}