src/NuGet.Clients/NuGet.PackageManagement.PowerShellCmdlets/PowerShellHost.cs (716 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using EnvDTE; using Microsoft; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; using NuGet.Commands; using NuGet.Common; using NuGet.Configuration; using NuGet.PackageManagement; using NuGet.PackageManagement.VisualStudio; using NuGet.Packaging.Core; using NuGet.ProjectManagement; using NuGet.Protocol.Core.Types; using NuGet.VisualStudio; using NuGet.VisualStudio.Common.Telemetry.PowerShell; using NuGet.VisualStudio.Telemetry; using LocalResources = NuGet.PackageManagement.PowerShellCmdlets.Resources; using Task = System.Threading.Tasks.Task; namespace NuGetConsole.Host.PowerShell.Implementation { internal abstract class PowerShellHost : IHost, IPathExpansion, IDisposable { private static readonly string AggregateSourceName = LocalResources.AggregateSourceName; private static readonly TimeSpan ExecuteInitScriptsRetryDelay = TimeSpan.FromMilliseconds(400); private const int MaxTasks = 16; private static bool PowerShellLoaded = false; private Microsoft.VisualStudio.Threading.AsyncLazy<IVsMonitorSelection> _vsMonitorSelection; #pragma warning disable RS0030 // Do not used banned APIs private readonly AsyncSemaphore _initScriptsLock = new AsyncSemaphore(1); #pragma warning restore RS0030 // Do not used banned APIs private readonly string _name; private readonly IRestoreEvents _restoreEvents; private readonly IRunspaceManager _runspaceManager; private readonly IEnvironmentVariableReader _environmentVariableReader; private readonly ISourceRepositoryProvider _sourceRepositoryProvider; private readonly Lazy<IVsSolutionManager> _solutionManager; private readonly Lazy<ISettings> _settings; private readonly Lazy<ISourceControlManagerProvider> _sourceControlManagerProvider; private readonly Lazy<ICommonOperations> _commonOperations; private readonly Lazy<IDeleteOnRestartManager> _deleteOnRestartManager; private readonly Lazy<IScriptExecutor> _scriptExecutor; private readonly Lazy<IRestoreProgressReporter> _restoreProgressReporter; private const string ActivePackageSourceKey = "activePackageSource"; private const string SyncModeKey = "IsSyncMode"; private const string PackageManagementContextKey = "PackageManagementContext"; private const string DTEKey = "DTE"; private const string CancellationTokenKey = "CancellationTokenKey"; private const int ExecuteInitScriptsRetriesLimit = 50; private string _activePackageSource; private string[] _packageSources; private readonly Lazy<DTE> _dte; private uint _solutionExistsCookie; private IConsole _activeConsole; private NuGetPSHost _nugetHost; // indicates whether this host has been initialized. // null = not initilized, true = initialized successfully, false = initialized unsuccessfully private bool? _initialized; public bool IsInitializedSuccessfully => _initialized.HasValue && _initialized.Value; // store the current (non-truncated) project names displayed in the project name combobox private string[] _projectSafeNames; // store the current command typed so far private ComplexCommand _complexCommand; // store the current CancellationTokenSource which will be used to cancel the operation // in case of abort private CancellationTokenSource _tokenSource; // store the current CancellationToken. This will be set on the private data private CancellationToken _token; // store the current solution directory which will be to check the solution change while executing init scripts. private string _currentSolutionDirectory; /// <summary> /// This field tracks information about the latest restore. /// </summary> private SolutionRestoredEventArgs _latestRestore; /// <summary> /// This field tracks information about the most recent restore that had scripts executed for it. /// </summary> private SolutionRestoredEventArgs _currentRestore; protected PowerShellHost(string name, IRestoreEvents restoreEvents, IRunspaceManager runspaceManager, IEnvironmentVariableReader environmentVariableReader) { _restoreEvents = restoreEvents; _runspaceManager = runspaceManager; _environmentVariableReader = environmentVariableReader ?? throw new ArgumentNullException(nameof(environmentVariableReader)); // TODO: Take these as ctor arguments var componentModel = NuGetUIThreadHelper.JoinableTaskFactory.Run(ServiceLocator.GetComponentModelAsync); _sourceRepositoryProvider = componentModel.GetService<ISourceRepositoryProvider>(); _solutionManager = new Lazy<IVsSolutionManager>(() => componentModel.GetService<IVsSolutionManager>()); _settings = new Lazy<ISettings>(() => componentModel.GetService<ISettings>()); _deleteOnRestartManager = new Lazy<IDeleteOnRestartManager>(() => componentModel.GetService<IDeleteOnRestartManager>()); _scriptExecutor = new Lazy<IScriptExecutor>(() => componentModel.GetService<IScriptExecutor>()); _restoreProgressReporter = new Lazy<IRestoreProgressReporter>(() => componentModel.GetService<IRestoreProgressReporter>()); _dte = new Lazy<DTE>(() => NuGetUIThreadHelper.JoinableTaskFactory.Run(() => ServiceLocator.GetGlobalServiceAsync<SDTE, DTE>())); _sourceControlManagerProvider = new Lazy<ISourceControlManagerProvider>( () => componentModel.GetService<ISourceControlManagerProvider>()); _commonOperations = new Lazy<ICommonOperations>(() => componentModel.GetService<ICommonOperations>()); _name = name; IsCommandEnabled = true; InitializeSources(); _sourceRepositoryProvider.PackageSourceProvider.PackageSourcesChanged += PackageSourceProvider_PackageSourcesChanged; _restoreEvents.SolutionRestoreCompleted += RestoreEvents_SolutionRestoreCompleted; _vsMonitorSelection = new Microsoft.VisualStudio.Threading.AsyncLazy<IVsMonitorSelection>( async () => { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // get the UI context cookie for the debugging mode var vsMonitorSelection = await ServiceLocator.GetGlobalServiceAsync<IVsMonitorSelection, IVsMonitorSelection>(); var guidCmdUI = VSConstants.UICONTEXT.SolutionExists_guid; vsMonitorSelection.GetCmdUIContextCookie( ref guidCmdUI, out _solutionExistsCookie); return vsMonitorSelection; }, ThreadHelper.JoinableTaskFactory); } private void InitializeSources() { _packageSources = GetEnabledPackageSources(_sourceRepositoryProvider); UpdateActiveSource(_sourceRepositoryProvider.PackageSourceProvider.ActivePackageSourceName); } private static string[] GetEnabledPackageSources(ISourceRepositoryProvider sourceRepositoryProvider) { var enabledSources = sourceRepositoryProvider .GetRepositories() .Where(r => r.PackageSource.IsEnabled) .ToArray(); var packageSources = new List<string>(); if (enabledSources.Length > 1) { packageSources.Add(AggregateSourceName); } packageSources.AddRange( enabledSources.Select(r => r.PackageSource.Name)); return packageSources.ToArray(); } #region Properties protected Pipeline ExecutingPipeline { get; set; } /// <summary> /// The host is associated with a particular console on a per-command basis. /// This gets set every time a command is executed on this host. /// </summary> protected IConsole ActiveConsole { get { return _activeConsole; } set { _activeConsole = value; if (_nugetHost != null) { _nugetHost.ActiveConsole = value; } } } public bool IsCommandEnabled { get; private set; } protected RunspaceDispatcher Runspace { get; private set; } private ComplexCommand ComplexCommand { get { if (_complexCommand == null) { _complexCommand = new ComplexCommand((allLines, lastLine) => { Collection<PSParseError> errors; PSParser.Tokenize(allLines, out errors); // If there is a parse error token whose END is past input END, consider // it a multi-line command. if (errors.Count > 0) { if (errors.Any(e => (e.Token.Start + e.Token.Length) >= allLines.Length)) { return false; } } return true; }); } return _complexCommand; } } public string Prompt { get { return ComplexCommand.IsComplete ? EvaluatePrompt() : ">> "; } } public PackageManagementContext PackageManagementContext { get { return new PackageManagementContext( _sourceRepositoryProvider, _solutionManager.Value, _settings.Value, _sourceControlManagerProvider.Value, _commonOperations.Value); } } public string ActivePackageSource { get { return _activePackageSource; } set { UpdateActiveSource(value); } } public string DefaultProject { get; private set; } #endregion [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] private string EvaluatePrompt() { var prompt = "PM>"; return NuGetUIThreadHelper.JoinableTaskFactory.Run(async delegate { try { // Execute the prompt function from a worker thread, so that the UI thread is not blocked waiting // on it. Note that a default prompt function as defined in Profile.ps1 will simply return // a string "PM>". This will always work. However, a custom "prompt" function might call // Write-Host and NuGet will explicity switch to the main thread using JTF. // If the main thread was blocked then, it will consistently make the UI stop responding var output = await Task.Run(() => Runspace.Invoke("prompt", null, outputResults: false).FirstOrDefault()); if (output != null) { var result = output.BaseObject.ToString(); if (!string.IsNullOrEmpty(result)) { prompt = result; } } } catch (Exception ex) { ExceptionHelper.WriteErrorToActivityLog(ex); } return prompt; }); } /// <summary> /// Doing all necessary initialization works before the console accepts user inputs /// </summary> [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public void Initialize(IConsole console) { NuGetUIThreadHelper.JoinableTaskFactory.Run(async delegate { ActiveConsole = console; if (_initialized.HasValue) { if (_initialized.Value && console.ShowDisclaimerHeader) { DisplayDisclaimerAndHelpText(); } } else { try { bool _isPmc = console is IWpfConsole; var result = _runspaceManager.GetRunspace(console, _name); Runspace = result.Item1; _nugetHost = result.Item2; _initialized = true; if (console.ShowDisclaimerHeader) { DisplayDisclaimerAndHelpText(); } UpdateWorkingDirectory(); if (!PowerShellLoaded) { var telemetryEvent = new PowerShellLoadedEvent(isPmc: _isPmc, psVersion: Runspace.PSVersion.ToString()); TelemetryActivity.EmitTelemetryEvent(telemetryEvent); PowerShellLoaded = true; } NuGetPowerShellUsage.RaisePowerShellLoadEvent(isPMC: _isPmc); await ExecuteInitScriptsAsync(); // check if PMC console is actually opened, then only hook to solution load/close events. if (_isPmc) { // Hook up solution events _solutionManager.Value.SolutionOpened += (_, __) => HandleSolutionOpened(); _solutionManager.Value.SolutionClosed += (o, e) => { UpdateWorkingDirectory(); DefaultProject = null; NuGetUIThreadHelper.JoinableTaskFactory.Run(CommandUiUtilities.InvalidateDefaultProjectAsync); }; } _solutionManager.Value.NuGetProjectAdded += (o, e) => UpdateWorkingDirectoryAndAvailableProjects(); _solutionManager.Value.NuGetProjectRenamed += (o, e) => UpdateWorkingDirectoryAndAvailableProjects(); _solutionManager.Value.NuGetProjectUpdated += (o, e) => UpdateWorkingDirectoryAndAvailableProjects(); _solutionManager.Value.NuGetProjectRemoved += (o, e) => { UpdateWorkingDirectoryAndAvailableProjects(); // When the previous default project has been removed, _solutionManager.DefaultNuGetProjectName becomes null if (_solutionManager.Value.DefaultNuGetProjectName == null) { // Change default project to the first one in the collection SetDefaultProjectIndex(0); } }; // Set available private data on Host SetPrivateDataOnHost(false); StartAsyncDefaultProjectUpdate(); } catch (Exception ex) { // catch all exception as we don't want it to crash VS _initialized = false; IsCommandEnabled = false; ReportError(ex); ExceptionHelper.WriteErrorToActivityLog(ex); } } }); } private void HandleSolutionOpened() { _scriptExecutor.Value.Reset(); // Solution opened event is raised on the UI thread // Go off the UI thread before calling likely expensive call of ExecuteInitScriptsAsync // Also, it uses semaphores, do not call it from the UI thread Task.Run(async () => { UpdateWorkingDirectory(); var retries = 0; while (retries < ExecuteInitScriptsRetriesLimit) { if (await _solutionManager.Value.IsAllProjectsNominatedAsync()) { await ExecuteInitScriptsAsync(); break; } await Task.Delay(ExecuteInitScriptsRetryDelay); retries++; } }) .ContinueWith(_ => StartAsyncDefaultProjectUpdate(), TaskContinuationOptions.OnlyOnRanToCompletion); } private void UpdateWorkingDirectoryAndAvailableProjects() { UpdateWorkingDirectory(); GetAvailableProjects(); StartAsyncDefaultProjectUpdate(); } private void UpdateWorkingDirectory() { NuGetUIThreadHelper.JoinableTaskFactory.Run(async () => { await TaskScheduler.Default; if (Runspace.RunspaceAvailability == RunspaceAvailability.Available) { // if there is no solution open, we set the active directory to be user profile folder var targetDir = await _solutionManager.Value.IsSolutionOpenAsync() ? await _solutionManager.Value.GetSolutionDirectoryAsync() : _environmentVariableReader.GetEnvironmentVariable("USERPROFILE"); Runspace.ChangePSDirectory(targetDir); } }); } private async Task ExecuteInitScriptsAsync() { // Fix for Bug 1426 Disallow ExecuteInitScripts from being executed concurrently by multiple threads. #pragma warning disable RS0030 // Do not used banned APIs using (await _initScriptsLock.EnterAsync()) { if (!await _solutionManager.Value.IsSolutionOpenAsync()) { return; } Debug.Assert(_settings != null); if (_settings == null) { return; } var latestRestore = _latestRestore; var latestSolutionDirectory = await _solutionManager.Value.GetSolutionDirectoryAsync(); if (ShouldNoOpDueToRestore(latestRestore) && ShouldNoOpDueToSolutionDirectory(latestSolutionDirectory)) { _currentRestore = latestRestore; _currentSolutionDirectory = latestSolutionDirectory; return; } // We may be enumerating packages from disk here. Always do it from a background thread. await TaskScheduler.Default; var packageManager = new NuGetPackageManager( _sourceRepositoryProvider, _settings.Value, _solutionManager.Value, _deleteOnRestartManager.Value, _restoreProgressReporter.Value); var enumerator = new InstalledPackageEnumerator(_solutionManager.Value, _settings.Value); var installedPackages = await enumerator.EnumeratePackagesAsync(packageManager, CancellationToken.None); foreach (var installedPackage in installedPackages) { await ExecuteInitPs1Async(installedPackage.InstallPath, installedPackage.Identity); } // We are done executing scripts, so record the restore and solution directory that we executed for. // This aids the no-op logic above. _currentRestore = latestRestore; _currentSolutionDirectory = latestSolutionDirectory; } #pragma warning restore RS0030 // Do not used banned APIs } private async Task ExecuteInitPs1Async(string installPath, PackageIdentity identity) { try { var toolsPath = Path.Combine(installPath, "tools"); if (Directory.Exists(toolsPath)) { AddPathToEnvironment(toolsPath, _environmentVariableReader); var scriptPath = Path.Combine(toolsPath, PowerShellScripts.Init); if (File.Exists(scriptPath)) { NuGetPowerShellUsage.RaiseInitPs1LoadEvent(isPMC: _activeConsole is IWpfConsole); if (_scriptExecutor.Value.TryMarkVisited(identity, PackageInitPS1State.FoundAndExecuted)) { // always execute init script on a background thread await TaskScheduler.Default; var request = new ScriptExecutionRequest(scriptPath, installPath, identity, project: null); Runspace.Invoke( request.BuildCommand(), request.BuildInput(), outputResults: true); return; } } } _scriptExecutor.Value.TryMarkVisited(identity, PackageInitPS1State.NotFound); } catch (Exception ex) { // If execution of an init.ps1 scripts fails, do not let it crash our console. ReportError(ex); ExceptionHelper.WriteErrorToActivityLog(ex); } } private static void AddPathToEnvironment(string path, IEnvironmentVariableReader environmentVariableReader) { var currentPath = environmentVariableReader.GetEnvironmentVariable("path"); var currentPaths = new HashSet<string>( currentPath.Split(Path.PathSeparator).Select(p => p.Trim()), StringComparer.OrdinalIgnoreCase); if (currentPaths.Add(path)) { var newPath = currentPath + Path.PathSeparator + path; #pragma warning disable RS0030 // Do not use banned APIs (This add a path to the PATH environment variable just for the PowerShell console session) Environment.SetEnvironmentVariable("path", newPath); #pragma warning restore RS0030 // Do not use banned APIs (This add a path to the PATH environment variable just for the PowerShell console session) } } protected abstract bool ExecuteHost(string fullCommand, string command, params object[] inputs); public bool Execute(IConsole console, string command, params object[] inputs) { if (console == null) { throw new ArgumentNullException(nameof(console)); } if (command == null) { throw new ArgumentNullException(nameof(command)); } NuGetPowerShellUsage.RaiseCommandExecuteEvent(isPMC: console is IWpfConsole); // since install.ps1/uninstall.ps1 could depend on init scripts, so we need to make sure // to run it once for each solution NuGetUIThreadHelper.JoinableTaskFactory.Run(async () => { await ExecuteInitScriptsAsync(); }); ActiveConsole = console; string fullCommand; if (ComplexCommand.AddLine(command, out fullCommand) && !string.IsNullOrEmpty(fullCommand)) { // create a new token source with each command since CTS aren't usable once cancelled. _tokenSource = new CancellationTokenSource(); _token = _tokenSource.Token; return ExecuteHost(fullCommand, command, inputs); } return false; // constructing multi-line command } protected void OnExecuteCommandEnd() { // dispose token source related to this current command _tokenSource?.Dispose(); _token = CancellationToken.None; } public void Abort() { ExecutingPipeline?.StopAsync(); ComplexCommand.Clear(); try { _tokenSource?.Cancel(); } catch (ObjectDisposedException) { // ObjectDisposedException is expected here, since at clear console command, tokenSource // would have already been disposed. } } protected void SetPrivateDataOnHost(bool isSync) { SetPropertyValueOnHost(SyncModeKey, isSync); SetPropertyValueOnHost(PackageManagementContextKey, PackageManagementContext); // "All" aggregate source in a context of PS command means no particular source is preferred, // in that case all enabled sources will be picked for a command execution. SetPropertyValueOnHost(ActivePackageSourceKey, ActivePackageSource != AggregateSourceName ? ActivePackageSource : string.Empty); SetPropertyValueOnHost(DTEKey, _dte.Value); SetPropertyValueOnHost(CancellationTokenKey, _token); } private void SetPropertyValueOnHost(string propertyName, object value) { if (_nugetHost != null) { var property = _nugetHost.PrivateData.Properties[propertyName]; if (property == null) { property = new PSNoteProperty(propertyName, value); _nugetHost.PrivateData.Properties.Add(property); } else { property.Value = value; } } } public void SetDefaultRunspace() { Runspace.MakeDefault(); } private void DisplayDisclaimerAndHelpText() { WriteLine(LocalResources.Console_DisclaimerText); WriteLine(); WriteLine(string.Format(CultureInfo.CurrentCulture, LocalResources.PowerShellHostTitle, _nugetHost.Version)); WriteLine(); WriteLine(LocalResources.Console_HelpText); WriteLine(); } protected void ReportError(ErrorRecord record) { WriteErrorLine(Runspace.ExtractErrorFromErrorRecord(record)); } protected void ReportError(Exception exception) { exception = ExceptionUtilities.Unwrap(exception); WriteErrorLine(exception.Message); } private void WriteErrorLine(string message) { if (ActiveConsole != null) { NuGetUIThreadHelper.JoinableTaskFactory.Run(() => ActiveConsole?.WriteAsync(message + Environment.NewLine, Colors.White, Colors.DarkRed)); } } private void WriteLine(string message = "") { if (ActiveConsole != null) { NuGetUIThreadHelper.JoinableTaskFactory.Run(() => ActiveConsole?.WriteLineAsync(message)); } } public string[] GetPackageSources() => _packageSources; private void PackageSourceProvider_PackageSourcesChanged(object sender, EventArgs e) { _packageSources = GetEnabledPackageSources(_sourceRepositoryProvider); UpdateActiveSource(ActivePackageSource); } private void RestoreEvents_SolutionRestoreCompleted(SolutionRestoredEventArgs args) { _latestRestore = args; } private bool ShouldNoOpDueToRestore(SolutionRestoredEventArgs latestRestore) { return _currentRestore != null && latestRestore != null && ( latestRestore.RestoreStatus == NuGetOperationStatus.NoOp || object.ReferenceEquals(_currentRestore, latestRestore) ); } private bool ShouldNoOpDueToSolutionDirectory(string latestSolutionDirectory) { return StringComparer.OrdinalIgnoreCase.Equals( _currentSolutionDirectory, latestSolutionDirectory); } private void UpdateActiveSource(string activePackageSource) { if (_packageSources.Length == 0) { _activePackageSource = string.Empty; } else if (activePackageSource == null) { // use the first enabled source as the active source _activePackageSource = _packageSources.First(); } else { var s = _packageSources.FirstOrDefault( p => StringComparer.OrdinalIgnoreCase.Equals(p, activePackageSource)); // if the old active source still exists. Keep it as the active source. // if the old active source does not exist any more. In this case, // use the first eneabled source as the active source. _activePackageSource = s ?? _packageSources.First(); } } public void SetDefaultProjectIndex(int selectedIndex) { Debug.Assert(_solutionManager.Value != null); if (_projectSafeNames != null && selectedIndex >= 0 && selectedIndex < _projectSafeNames.Length) { _solutionManager.Value.DefaultNuGetProjectName = _projectSafeNames[selectedIndex]; } else { _solutionManager.Value.DefaultNuGetProjectName = null; } StartAsyncDefaultProjectUpdate(); } private void StartAsyncDefaultProjectUpdate() { Assumes.Present(_solutionManager.Value); NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async () => { await TaskScheduler.Default; NuGetProject project = await _solutionManager.Value.GetDefaultNuGetProjectAsync(); var oldValue = DefaultProject; string newValue; if (oldValue == null && project == null) { return; } else if (project == null) { newValue = null; } else { newValue = await GetDisplayNameAsync(project); } bool isInvalidationRequired = oldValue != newValue; if (isInvalidationRequired) { DefaultProject = newValue; await CommandUiUtilities.InvalidateDefaultProjectAsync(); } }) .PostOnFailure(nameof(PowerShellHost), nameof(StartAsyncDefaultProjectUpdate)); } public string[] GetAvailableProjects() { Debug.Assert(_solutionManager.Value != null); return NuGetUIThreadHelper.JoinableTaskFactory.Run(async delegate { var safeAndDisplayName = new List<Tuple<string, string>>(); var safeAndDisplayNameTasks = new List<Task<Tuple<string, string>>>(); var allProjects = await _solutionManager.Value.GetNuGetProjectsAsync(); var tasks = allProjects.Select( async e => { var safeName = await _solutionManager.Value.GetNuGetProjectSafeNameAsync(e); var displayName = await GetDisplayNameAsync(e); return Tuple.Create(safeName, displayName); }); foreach (var task in tasks) { // Throttle and wait for a task to finish if we have hit the limit if (safeAndDisplayNameTasks.Count == MaxTasks) { var displayName = await CompleteTaskAsync(safeAndDisplayNameTasks); safeAndDisplayName.Add(displayName); } safeAndDisplayNameTasks.Add(task); } // wait until all the tasks to retrieve display names are completed while (safeAndDisplayNameTasks.Count > 0) { var displayName = await CompleteTaskAsync(safeAndDisplayNameTasks); safeAndDisplayName.Add(displayName); } // Sort with respect to the DisplayName var sortedDisplayNames = safeAndDisplayName.OrderBy(i => i.Item2, StringComparer.CurrentCultureIgnoreCase).ToArray(); _projectSafeNames = sortedDisplayNames.Select(e => e.Item1).ToArray(); return _projectSafeNames; }); } private async Task<string> GetDisplayNameAsync(NuGetProject nuGetProject) { var vsProjectAdapter = await _solutionManager.Value.GetVsProjectAdapterAsync(nuGetProject); var name = vsProjectAdapter.CustomUniqueName; if (await IsWebSiteAsync(vsProjectAdapter)) { name = PathHelper.SmartTruncate(name, 40); } return name; } private async Task<bool> IsWebSiteAsync(IVsProjectAdapter project) { await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); return project.GetProjectTypeGuids().Contains(VsProjectTypes.WebSiteProjectTypeGuid); } private async Task<Tuple<string, string>> CompleteTaskAsync(List<Task<Tuple<string, string>>> nameTasks) { var doneTask = await Task.WhenAny(nameTasks); nameTasks.Remove(doneTask); return await doneTask; } #region ITabExpansion public Task<string[]> GetExpansionsAsync(string line, string lastWord, CancellationToken token) { return GetExpansionsAsyncCore(line, lastWord, token); } protected abstract Task<string[]> GetExpansionsAsyncCore(string line, string lastWord, CancellationToken token); protected async Task<string[]> GetExpansionsAsyncCore(string line, string lastWord, bool isSync, CancellationToken token) { // Set the _token object to the CancellationToken passed in, so that the Private Data can be set with this token // Powershell cmdlets will pick up the CancellationToken from the private data of the Host, and use it in their calls to NuGetPackageManager _token = token; string[] expansions; try { SetPrivateDataOnHost(isSync); expansions = await Task.Run(() => { var query = from s in Runspace.Invoke( @"$__pc_args=@();$input|%{$__pc_args+=$_};if(Test-Path Function:\TabExpansion2){(TabExpansion2 $__pc_args[0] $__pc_args[0].length).CompletionMatches|%{$_.CompletionText}}else{TabExpansion $__pc_args[0] $__pc_args[1]};Remove-Variable __pc_args -Scope 0;", new[] { line, lastWord }, outputResults: false) select (s == null ? null : s.ToString()); return query.ToArray(); }, _token); } finally { // Set the _token object to the CancellationToken passed in, so that the Private Data can be set correctly _token = CancellationToken.None; } return expansions; } #endregion #region IPathExpansion public Task<SimpleExpansion> GetPathExpansionsAsync(string line, CancellationToken token) { return GetPathExpansionsAsyncCore(line, token); } protected abstract Task<SimpleExpansion> GetPathExpansionsAsyncCore(string line, CancellationToken token); protected async Task<SimpleExpansion> GetPathExpansionsAsyncCore(string line, bool isSync, CancellationToken token) { // Set the _token object to the CancellationToken passed in, so that the Private Data can be set with this token // Powershell cmdlets will pick up the CancellationToken from the private data of the Host, and use it in their calls to NuGetPackageManager _token = token; SetPropertyValueOnHost(CancellationTokenKey, _token); var simpleExpansion = await Task.Run(() => { var expansion = Runspace.Invoke( "$input|%{$__pc_args=$_}; _TabExpansionPath $__pc_args; Remove-Variable __pc_args -Scope 0", new object[] { line }, outputResults: false).FirstOrDefault(); if (expansion != null) { var replaceStart = (int)expansion.Properties["ReplaceStart"].Value; IList<string> paths = ((IEnumerable<object>)expansion.Properties["Paths"].Value).Select(o => o.ToString()).ToList(); return new SimpleExpansion(replaceStart, line.Length - replaceStart, paths); } return null; }, token); _token = CancellationToken.None; SetPropertyValueOnHost(CancellationTokenKey, _token); return simpleExpansion; } #endregion #region IDisposable public void Dispose() { _restoreEvents.SolutionRestoreCompleted -= RestoreEvents_SolutionRestoreCompleted; #pragma warning disable RS0030 // Do not used banned APIs _initScriptsLock.Dispose(); #pragma warning restore RS0030 // Do not used banned APIs Runspace?.Dispose(); _tokenSource?.Dispose(); } #endregion } }