diff --git a/NTFSSecurity/EnumArgumentCompleters.cs b/NTFSSecurity/EnumArgumentCompleters.cs index 87604d6..927d1ea 100644 --- a/NTFSSecurity/EnumArgumentCompleters.cs +++ b/NTFSSecurity/EnumArgumentCompleters.cs @@ -13,21 +13,40 @@ namespace NTFSSecurity internal static class EnumArgumentCompletion { /// - /// Returns the names of the enumeration that start with the word the user already typed, compared without - /// regard to case, in the order Enum.GetNames returns them. + /// Returns the names of the enumeration that match the word the user already typed, compared without + /// regard to case, in the order Enum.GetNames returns them. A leading quote is stripped before matching, + /// together with the matching trailing quote that PowerShell adds to a word it completes while the quote + /// is still open, and every match is returned quoted the same way, so that completing inside an open quote + /// keeps the command line valid. The word itself may already contain a wildcard, such as "*Only" or "D*", + /// the same as PowerShell completes a parameter of an enumeration type. /// /// The enumeration to complete the names of. /// The word the user already typed; empty or null completes every name. internal static IEnumerable Complete(Type enumType, string wordToComplete) { var word = wordToComplete ?? string.Empty; + char? quote = null; + + if (word.Length > 0 && (word[0] == '\'' || word[0] == '"')) + { + quote = word[0]; + word = word.Substring(1); + + if (word.Length > 0 && word[word.Length - 1] == quote.Value) + { + word = word.Substring(0, word.Length - 1); + } + } + + var pattern = new WildcardPattern(word + "*", WildcardOptions.IgnoreCase); var results = new List(); foreach (var name in Enum.GetNames(enumType)) { - if (name.StartsWith(word, StringComparison.OrdinalIgnoreCase)) + if (pattern.IsMatch(name)) { - results.Add(new CompletionResult(name, name, CompletionResultType.ParameterValue, name)); + var completionText = quote.HasValue ? quote.Value + name + quote.Value : name; + results.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterValue, name)); } } diff --git a/Tests/ParameterSets.Access.Tests.ps1 b/Tests/ParameterSets.Access.Tests.ps1 index c3fd1c9..11189f1 100644 --- a/Tests/ParameterSets.Access.Tests.ps1 +++ b/Tests/ParameterSets.Access.Tests.ps1 @@ -1420,6 +1420,17 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; EnumTypeName = 'System.Security.AccessControl.PropagationFlags' } @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' } } + + # R1-4: a quoted or a wildcard word completed in rc8 (a typed parameter of an enumeration); the first + # EnumArgumentCompleters.cs returned nothing for either shape. Each ExpectedName has no sibling name that + # also starts with it (ThisFolderOnly has no ThisFolderOnlyOneLevel), so the unquoted and quoted forms match + # it alone; the wildcard only needs to contain it among the matches. + $accessCompletionFormCases = @( + @{ Parameter = 'AccessType'; Word = 'D'; ExpectedName = 'Deny' } + @{ Parameter = 'InheritanceFlags'; Word = 'Container'; ExpectedName = 'ContainerInherit' } + @{ Parameter = 'PropagationFlags'; Word = 'Inh'; ExpectedName = 'InheritOnly' } + @{ Parameter = 'AppliesTo'; Word = 'ThisFolderO'; ExpectedName = 'ThisFolderOnly' } + ) } Context 'A bad value bound from the pipeline' { @@ -1753,6 +1764,26 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF It ' should declare - as [object]' -ForEach $accessCompleterCases { (Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object]) } + + It 'Should complete Add-NTFSAccess - for the unquoted, single-quoted, double-quoted, and wildcard forms of ' -ForEach $accessCompletionFormCases { + $base = 'Add-NTFSAccess -Path C:\x -Account a -AccessRights Read -{0} ' -f $Parameter + + $unquoted = $base + $Word + $r1 = [System.Management.Automation.CommandCompletion]::CompleteInput($unquoted, $unquoted.Length, $null) + @($r1.CompletionMatches.CompletionText) | Should -Contain $ExpectedName + + $singleQuoted = $base + "'" + $Word + $r2 = [System.Management.Automation.CommandCompletion]::CompleteInput($singleQuoted, $singleQuoted.Length, $null) + @($r2.CompletionMatches.CompletionText) | Should -Contain ("'" + $ExpectedName + "'") + + $doubleQuoted = $base + '"' + $Word + $r3 = [System.Management.Automation.CommandCompletion]::CompleteInput($doubleQuoted, $doubleQuoted.Length, $null) + @($r3.CompletionMatches.CompletionText) | Should -Contain ('"' + $ExpectedName + '"') + + $wildcard = $base + '*' + $ExpectedName.Substring($ExpectedName.Length - 3) + $r4 = [System.Management.Automation.CommandCompletion]::CompleteInput($wildcard, $wildcard.Length, $null) + @($r4.CompletionMatches.CompletionText) | Should -Contain $ExpectedName + } } } diff --git a/Tests/ParameterSets.Audit.Tests.ps1 b/Tests/ParameterSets.Audit.Tests.ps1 index fb2cb0d..f3c6868 100644 --- a/Tests/ParameterSets.Audit.Tests.ps1 +++ b/Tests/ParameterSets.Audit.Tests.ps1 @@ -1092,6 +1092,13 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; EnumTypeName = 'System.Security.AccessControl.PropagationFlags' } @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' } } + + # R1-4: AuditFlags is the one completer that ParameterSets.Access.Tests.ps1 does not already cover with the + # quoted and wildcard forms (it shares the same completer code for InheritanceFlags, PropagationFlags, and + # AppliesTo, so those are pinned there). + $auditCompletionFormCases = @( + @{ Parameter = 'AuditFlags'; Word = 'F'; ExpectedName = 'Failure' } + ) } Context 'A bad value bound from the pipeline' { @@ -1413,5 +1420,25 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF It ' should declare - as [object]' -ForEach $auditCompleterCases { (Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object]) } + + It 'Should complete Add-NTFSAudit - for the unquoted, single-quoted, double-quoted, and wildcard forms of ' -ForEach $auditCompletionFormCases { + $base = 'Add-NTFSAudit -Path C:\x -Account a -AccessRights Read -{0} ' -f $Parameter + + $unquoted = $base + $Word + $r1 = [System.Management.Automation.CommandCompletion]::CompleteInput($unquoted, $unquoted.Length, $null) + @($r1.CompletionMatches.CompletionText) | Should -Contain $ExpectedName + + $singleQuoted = $base + "'" + $Word + $r2 = [System.Management.Automation.CommandCompletion]::CompleteInput($singleQuoted, $singleQuoted.Length, $null) + @($r2.CompletionMatches.CompletionText) | Should -Contain ("'" + $ExpectedName + "'") + + $doubleQuoted = $base + '"' + $Word + $r3 = [System.Management.Automation.CommandCompletion]::CompleteInput($doubleQuoted, $doubleQuoted.Length, $null) + @($r3.CompletionMatches.CompletionText) | Should -Contain ('"' + $ExpectedName + '"') + + $wildcard = $base + '*' + $ExpectedName.Substring($ExpectedName.Length - 3) + $r4 = [System.Management.Automation.CommandCompletion]::CompleteInput($wildcard, $wildcard.Length, $null) + @($r4.CompletionMatches.CompletionText) | Should -Contain $ExpectedName + } } }