Browse Source

fix(access): complete quoted and wildcard words of the five parameters

The completers for AccessType, AuditFlags, InheritanceFlags,
PropagationFlags, and AppliesTo returned nothing for a quoted word
(-AccessType "D) or a wildcard word (-AppliesTo *Only, -AccessType
D*), which rc8's built-in enum-parameter completion still offered.
PowerShell auto-closes an unterminated quote before it reaches the
completer, so the word a completer receives for -AccessType "D is
"D", not D.

EnumArgumentCompletion.Complete now strips a leading quote and its
matching trailing quote from the word before matching, matches the
enumeration's names with WildcardPattern(word + "*",
WildcardOptions.IgnoreCase) the way PowerShell's own enum completion
does, and, when the word was quoted, returns each match in the same
quote character (CompletionText 'FilesOnly' for a leading single
quote). An unquoted, non-wildcard word is unaffected: appending "*"
to it only turns an exact prefix match into a prefix-wildcard match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/131/head
Raimund Andree 1 week ago
parent
commit
e75bc15953
  1. 27
      NTFSSecurity/EnumArgumentCompleters.cs
  2. 31
      Tests/ParameterSets.Access.Tests.ps1
  3. 27
      Tests/ParameterSets.Audit.Tests.ps1

27
NTFSSecurity/EnumArgumentCompleters.cs

@ -13,21 +13,40 @@ namespace NTFSSecurity
internal static class EnumArgumentCompletion
{
/// <summary>
/// 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.
/// </summary>
/// <param name="enumType">The enumeration to complete the names of.</param>
/// <param name="wordToComplete">The word the user already typed; empty or null completes every name.</param>
internal static IEnumerable<CompletionResult> 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<CompletionResult>();
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));
}
}

31
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 '<Cmdlet> should declare -<Parameter> as [object]' -ForEach $accessCompleterCases {
(Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object])
}
It 'Should complete Add-NTFSAccess -<Parameter> for the unquoted, single-quoted, double-quoted, and wildcard forms of <ExpectedName>' -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
}
}
}

27
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 '<Cmdlet> should declare -<Parameter> as [object]' -ForEach $auditCompleterCases {
(Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object])
}
It 'Should complete Add-NTFSAudit -<Parameter> for the unquoted, single-quoted, double-quoted, and wildcard forms of <ExpectedName>' -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
}
}
}

Loading…
Cancel
Save