mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add -Verb argument completer for Start-Process (#20415)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel; // Win32Exception
|
||||
using System.Diagnostics; // Process class
|
||||
@@ -11,6 +12,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Language;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Serialization;
|
||||
@@ -1800,6 +1802,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </remarks>
|
||||
[Parameter(ParameterSetName = "UseShellExecute")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
[ArgumentCompleter(typeof(VerbArgumentCompleter))]
|
||||
public string Verb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -2656,6 +2659,95 @@ namespace Microsoft.PowerShell.Commands
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides argument completion for Verb parameter.
|
||||
/// </summary>
|
||||
public class VerbArgumentCompleter : IArgumentCompleter
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns completion results for verb parameter.
|
||||
/// </summary>
|
||||
/// <param name="commandName">The command name.</param>
|
||||
/// <param name="parameterName">The parameter name.</param>
|
||||
/// <param name="wordToComplete">The word to complete.</param>
|
||||
/// <param name="commandAst">The command AST.</param>
|
||||
/// <param name="fakeBoundParameters">The fake bound parameters.</param>
|
||||
/// <returns>List of Completion Results.</returns>
|
||||
public IEnumerable<CompletionResult> CompleteArgument(
|
||||
string commandName,
|
||||
string parameterName,
|
||||
string wordToComplete,
|
||||
CommandAst commandAst,
|
||||
IDictionary fakeBoundParameters)
|
||||
{
|
||||
// -Verb is not supported on non-Windows platforms as well as Windows headless SKUs
|
||||
if (!Platform.IsWindowsDesktop)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Completion: Start-Process -FilePath <path> -Verb <wordToComplete>
|
||||
if (commandName.Equals("Start-Process", StringComparison.OrdinalIgnoreCase)
|
||||
&& fakeBoundParameters.Contains("FilePath"))
|
||||
{
|
||||
string filePath = fakeBoundParameters["FilePath"].ToString();
|
||||
|
||||
// Complete file verbs if extension exists
|
||||
if (Path.HasExtension(filePath))
|
||||
{
|
||||
foreach (string verb in CompleteFileVerbs(filePath, wordToComplete))
|
||||
{
|
||||
yield return new CompletionResult(verb);
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Otherwise check if command is an Application to resolve executable full path with extension
|
||||
// e.g if powershell was given, resolve to powershell.exe to get verbs
|
||||
using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
|
||||
|
||||
var commandInfo = new CmdletInfo("Get-Command", typeof(GetCommandCommand));
|
||||
|
||||
ps.AddCommand(commandInfo);
|
||||
ps.AddParameter("Name", filePath);
|
||||
ps.AddParameter("CommandType", CommandTypes.Application);
|
||||
|
||||
Collection<CommandInfo> commands = ps.Invoke<CommandInfo>();
|
||||
|
||||
// Start-Process & Get-Command select first found application based on PATHEXT environment variable
|
||||
if (commands.Count >= 1)
|
||||
{
|
||||
foreach (string verb in CompleteFileVerbs(commands[0].Source, wordToComplete))
|
||||
{
|
||||
yield return new CompletionResult(verb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes file verbs.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to get verbs.</param>
|
||||
/// <param name="wordToComplete">The word to complete.</param>
|
||||
/// <returns>List of file verbs to complete.</returns>
|
||||
private static IEnumerable<string> CompleteFileVerbs(string filePath, string wordToComplete)
|
||||
{
|
||||
var verbPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
|
||||
|
||||
string[] verbs = new ProcessStartInfo(filePath).Verbs;
|
||||
|
||||
foreach (string verb in verbs)
|
||||
{
|
||||
if (verbPattern.IsMatch(verb))
|
||||
{
|
||||
yield return verb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNIX
|
||||
/// <summary>
|
||||
/// ProcessCollection is a helper class used by Start-Process -Wait cmdlet to monitor the
|
||||
|
||||
@@ -744,6 +744,46 @@ ConstructorTestClass(int i, bool b)
|
||||
$res.CompletionMatches[0].CompletionText | Should -BeExactly '$TestVar1'
|
||||
}
|
||||
|
||||
Context 'Start-Process -Verb parameter completion' {
|
||||
BeforeAll {
|
||||
function GetProcessInfoVerbs($path) {
|
||||
(New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList $path).Verbs
|
||||
}
|
||||
|
||||
$cmdPath = Join-Path -Path $TestDrive -ChildPath 'test.cmd'
|
||||
$cmdVerbs = GetProcessInfoVerbs $cmdPath
|
||||
$exePath = Join-Path -Path $TestDrive -ChildPath 'test.exe'
|
||||
$exeVerbs = GetProcessInfoVerbs $exePath
|
||||
$exeVerbsStartingWithRun = $exeVerbs | Where-Object { $_ -like 'run*' }
|
||||
$powerShellExeWithNoExtension = 'powershell'
|
||||
$txtPath = Join-Path -Path $TestDrive -ChildPath 'test.txt'
|
||||
$txtVerbs = GetProcessInfoVerbs $txtPath
|
||||
$wavPath = Join-Path -Path $TestDrive -ChildPath 'test.wav'
|
||||
$wavVerbs = GetProcessInfoVerbs $wavPath
|
||||
$docxPath = Join-Path -Path $TestDrive -ChildPath 'test.docx'
|
||||
$docxVerbs = GetProcessInfoVerbs $docxPath
|
||||
$fileWithNoExtensionPath = Join-Path -Path $TestDrive -ChildPath 'test'
|
||||
$fileWithNoExtensionVerbs = GetProcessInfoVerbs $fileWithNoExtensionPath
|
||||
}
|
||||
|
||||
It "Should complete Verb parameter for '<TextInput>'" -Skip:(!([System.Management.Automation.Platform]::IsWindowsDesktop)) -TestCases @(
|
||||
@{ TextInput = 'Start-Process -Verb '; ExpectedVerbs = '' }
|
||||
@{ TextInput = "Start-Process -FilePath $cmdPath -Verb "; ExpectedVerbs = $cmdVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $exePath -Verb "; ExpectedVerbs = $exeVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $exePath -Verb run"; ExpectedVerbs = $exeVerbsStartingWithRun -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $powerShellExeWithNoExtension -Verb "; ExpectedVerbs = $exeVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $txtPath -Verb "; ExpectedVerbs = $txtVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $wavPath -Verb "; ExpectedVerbs = $wavVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $docxPath -Verb "; ExpectedVerbs = $docxVerbs -join ' ' }
|
||||
@{ TextInput = "Start-Process -FilePath $fileWithNoExtensionPath -Verb "; ExpectedVerbs = $fileWithNoExtensionVerbs -join ' ' }
|
||||
) {
|
||||
param($TextInput, $ExpectedVerbs)
|
||||
$res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length
|
||||
$completionText = $res.CompletionMatches.CompletionText | Sort-Object
|
||||
$completionText -join ' ' | Should -BeExactly $ExpectedVerbs
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Scope parameter completion' {
|
||||
BeforeAll {
|
||||
$allScopes = 'Global Local Script'
|
||||
|
||||
Reference in New Issue
Block a user