diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs index b2b9e6c1a8..d95540d21d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs @@ -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 /// [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] +#if !UNIX + [ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))] +#endif public string PropertyType { get; set; } /// @@ -175,4 +182,118 @@ namespace Microsoft.PowerShell.Commands #endregion Command code } + +#if !UNIX + /// + /// Provides argument completion for PropertyType parameter. + /// + 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 + }; + + /// + /// Returns completion results for PropertyType parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable 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)); + } + } + } + + /// + /// Checks if parameter paths are from Registry provider. + /// + /// The fake bound parameters. + /// Boolean indicating if paths are from Registry Provider. + private static bool IsRegistryProvider(IDictionary fakeBoundParameters) + { + Collection 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"); + } + + /// + /// Resolve path or literal path using Resolve-Path. + /// + /// The path to resolve. + /// Specifies if path is literal path. + /// Collection of Pathinfo objects. + private static Collection 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 output = ps.Invoke(); + + return output; + } + } +#endif } diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 26d4dc4e14..73399a6d6e 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -353,4 +353,25 @@ using namespace <AliasName> = <.NET-namespace> using type <AliasName> = <.NET-type> + + A normal string. + + + A string that contains unexpanded references to environment variables that are expanded when the value is retrieved. + + + Binary data in any form. + + + A 32-bit binary number. + + + An array of strings. + + + A 64-bit binary number. + + + An unsupported registry data type. + diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index ce278d13b4..743ab6465f 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -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 ''" -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 = @'