Add -PropertyType argument completer for New-ItemProperty (#21117)

This commit is contained in:
Armaan Mcleod
2024-12-25 17:27:44 +05:00
committed by GitHub
parent 8017195541
commit e127345820
3 changed files with 223 additions and 0 deletions
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Language;
namespace Microsoft.PowerShell.Commands
{
@@ -63,6 +67,9 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
[Alias("Type")]
#if !UNIX
[ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))]
#endif
public string PropertyType { get; set; }
/// <summary>
@@ -175,4 +182,118 @@ namespace Microsoft.PowerShell.Commands
#endregion Command code
}
#if !UNIX
/// <summary>
/// Provides argument completion for PropertyType parameter.
/// </summary>
public class PropertyTypeArgumentCompleter : IArgumentCompleter
{
private static readonly string[] s_RegistryPropertyTypes = new string[]
{
"String",
"ExpandString",
"Binary",
"DWord",
"MultiString",
"QWord",
"Unknown"
};
private static string GetRegistryPropertyTypeToolTip(string propertyTypeName) => propertyTypeName switch
{
"String" => TabCompletionStrings.RegistryStringToolTip,
"ExpandString" => TabCompletionStrings.RegistryExpandStringToolTip,
"Binary" => TabCompletionStrings.RegistryBinaryToolTip,
"DWord" => TabCompletionStrings.RegistryDWordToolTip,
"MultiString" => TabCompletionStrings.RegistryMultiStringToolTip,
"QWord" => TabCompletionStrings.RegistryQWordToolTip,
_ => TabCompletionStrings.RegistryUnknownToolTip
};
/// <summary>
/// Returns completion results for PropertyType 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)
{
if (!IsRegistryProvider(fakeBoundParameters))
{
yield break;
}
string quote = CompletionCompleters.HandleDoubleAndSingleQuote(ref wordToComplete);
var propertyTypePattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
foreach (string propertyType in s_RegistryPropertyTypes)
{
if (propertyTypePattern.IsMatch(propertyType))
{
string completionText = quote == string.Empty
? propertyType
: quote + propertyType + quote;
yield return new CompletionResult(
completionText,
propertyType,
CompletionResultType.ParameterValue,
GetRegistryPropertyTypeToolTip(propertyType));
}
}
}
/// <summary>
/// Checks if parameter paths are from Registry provider.
/// </summary>
/// <param name="fakeBoundParameters">The fake bound parameters.</param>
/// <returns>Boolean indicating if paths are from Registry Provider.</returns>
private static bool IsRegistryProvider(IDictionary fakeBoundParameters)
{
Collection<PathInfo> paths;
if (fakeBoundParameters.Contains("Path"))
{
paths = ResolvePath(fakeBoundParameters["Path"], isLiteralPath: false);
}
else if (fakeBoundParameters.Contains("LiteralPath"))
{
paths = ResolvePath(fakeBoundParameters["LiteralPath"], isLiteralPath: true);
}
else
{
paths = ResolvePath(@".\", isLiteralPath: false);
}
return paths.Count > 0 && paths[0].Provider.NameEquals("Registry");
}
/// <summary>
/// Resolve path or literal path using Resolve-Path.
/// </summary>
/// <param name="path">The path to resolve.</param>
/// <param name="isLiteralPath">Specifies if path is literal path.</param>
/// <returns>Collection of Pathinfo objects.</returns>
private static Collection<PathInfo> ResolvePath(object path, bool isLiteralPath)
{
using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
ps.AddCommand("Microsoft.PowerShell.Management\\Resolve-Path");
ps.AddParameter(isLiteralPath ? "LiteralPath" : "Path", path);
Collection<PathInfo> output = ps.Invoke<PathInfo>();
return output;
}
}
#endif
}
@@ -353,4 +353,25 @@ using namespace &lt;AliasName&gt; = &lt;.NET-namespace&gt;</value>
using type &lt;AliasName&gt; = &lt;.NET-type&gt;</value>
</data>
<data name="RegistryStringToolTip" xml:space="preserve">
<value>A normal string.</value>
</data>
<data name="RegistryExpandStringToolTip" xml:space="preserve">
<value>A string that contains unexpanded references to environment variables that are expanded when the value is retrieved.</value>
</data>
<data name="RegistryBinaryToolTip" xml:space="preserve">
<value>Binary data in any form.</value>
</data>
<data name="RegistryDWordToolTip" xml:space="preserve">
<value>A 32-bit binary number.</value>
</data>
<data name="RegistryMultiStringToolTip" xml:space="preserve">
<value>An array of strings.</value>
</data>
<data name="RegistryQWordToolTip" xml:space="preserve">
<value>A 64-bit binary number.</value>
</data>
<data name="RegistryUnknownToolTip" xml:space="preserve">
<value>An unsupported registry data type.</value>
</data>
</root>
@@ -931,6 +931,87 @@ ConstructorTestClass(int i, bool b)
}
}
Context 'New-ItemProperty -PropertyType parameter completion' {
BeforeAll {
if ($IsWindows) {
$allRegistryValueKinds = 'String ExpandString Binary DWord MultiString QWord Unknown'
$allRegistryValueKindsWithQuotes = "'String' 'ExpandString' 'Binary' 'DWord' 'MultiString' 'QWord' 'Unknown'"
$dwordValueKind = 'DWord'
$qwordValueKind = 'QWord'
$binaryValueKind = 'Binary'
$multiStringValueKind = 'MultiString'
$registryPath = "HKCU:\test1\sub"
New-Item -Path $registryPath -Force
$registryLiteralPath = "HKCU:\test2\*\sub"
New-Item -Path $registryLiteralPath -Force
$fileSystemPath = "TestDrive:\test1.txt"
New-Item -Path $fileSystemPath -Force
$fileSystemLiteralPathDir = "TestDrive:\[]"
$fileSystemLiteralPath = "$fileSystemLiteralPathDir\test2.txt"
New-Item -Path $fileSystemLiteralPath -Force
}
}
It "Should complete Property Type for '<TextInput>'" -Skip:(!$IsWindows) -TestCases @(
# -Path completions
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType "; ExpectedPropertyTypes = $allRegistryValueKinds }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType d"; ExpectedPropertyTypes = $dwordValueKind }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType q"; ExpectedPropertyTypes = $qwordValueKind }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType bin"; ExpectedPropertyTypes = $binaryValueKind }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType multi"; ExpectedPropertyTypes = $multiStringValueKind }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType invalidproptype"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -Path $fileSystemPath -PropertyType "; ExpectedPropertyTypes = '' }
# -LiteralPath completions
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType "; ExpectedPropertyTypes = $allRegistryValueKinds }
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType d"; ExpectedPropertyTypes = $dwordValueKind }
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType q"; ExpectedPropertyTypes = $qwordValueKind }
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType bin"; ExpectedPropertyTypes = $binaryValueKind }
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType multi"; ExpectedPropertyTypes = $multiStringValueKind }
@{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType invalidproptype"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -LiteralPath $fileSystemLiteralPath -PropertyType "; ExpectedPropertyTypes = '' }
# All of these should return no completion since they don't specify -Path/-LiteralPath
@{ TextInput = "New-ItemProperty -PropertyType "; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -PropertyType d"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -PropertyType q"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -PropertyType bin"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -PropertyType multi"; ExpectedPropertyTypes = '' }
@{ TextInput = "New-ItemProperty -PropertyType invalidproptype"; ExpectedPropertyTypes = '' }
# All of these should return completion even with quotes included
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType '"; ExpectedPropertyTypes = $allRegistryValueKindsWithQuotes }
@{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType 'bin"; ExpectedPropertyTypes = "'$binaryValueKind'" }
) {
param($TextInput, $ExpectedPropertyTypes)
$res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length
$completionText = $res.CompletionMatches.CompletionText
$completionText -join ' ' | Should -BeExactly $ExpectedPropertyTypes
}
It "Test fallback to provider of current location if no path specified" -Skip:(!$IsWindows) {
try {
Push-Location HKCU:\
$textInput = "New-ItemProperty -PropertyType "
$res = TabExpansion2 -inputScript $textInput -cursorColumn $textInput.Length
$completionText = $res.CompletionMatches.CompletionText
$completionText -join ' ' | Should -BeExactly $allRegistryValueKinds
}
finally {
Pop-Location
}
}
AfterAll {
if ($IsWindows) {
Remove-Item -Path $registryPath -Force
Remove-Item -LiteralPath $registryLiteralPath -Force
Remove-Item -Path $fileSystemPath -Force
Remove-Item -LiteralPath $fileSystemLiteralPathDir -Recurse -Force
}
}
}
Context "Format cmdlet's View paramter completion" {
BeforeAll {
$viewDefinition = @'