Skip to content

Commit 94a71b0

Browse files
dlwyattdaxian-dbw
authored andcommitted
Fix ValueFromRemainingArguments to have consistent behavior between script and C# (PowerShell#2038)
1 parent 2a9cd72 commit 94a71b0

3 files changed

Lines changed: 112 additions & 22 deletions

File tree

src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write-Object.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,8 @@ protected override void ProcessRecord()
5555
{
5656
enumerate = false;
5757
}
58-
foreach (PSObject inputObject in _inputObjects) // compensate for ValueFromRemainingArguments
59-
{
60-
WriteObject(inputObject, enumerate);
61-
}
58+
59+
WriteObject(_inputObjects, enumerate);
6260
}//processrecord
6361
}//WriteOutputCommand
6462
#endregion

src/System.Management.Automation/engine/CmdletParameterBinderController.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,33 +1684,31 @@ private void HandleRemainingArguments()
16841684
var cpi = CommandParameterInternal.CreateParameterWithArgument(
16851685
PositionUtilities.EmptyExtent, varargsParameter.Parameter.Name, "-" + varargsParameter.Parameter.Name + ":",
16861686
argumentExtent, valueFromRemainingArguments, false);
1687+
1688+
// To make all of the following work similarly (the first is handled elsewhere, but second and third are
1689+
// handled here):
1690+
// Set-ClusterOwnerNode -Owners foo,bar
1691+
// Set-ClusterOwnerNode foo bar
1692+
// Set-ClusterOwnerNode foo,bar
1693+
// we unwrap our List, but only if there is a single argument of type object[].
1694+
if (valueFromRemainingArguments.Count == 1 && valueFromRemainingArguments[0] is object[])
1695+
{
1696+
cpi.SetArgumentValue(UnboundArguments[0].ArgumentExtent, valueFromRemainingArguments[0]);
1697+
}
1698+
16871699
try
16881700
{
16891701
BindParameter(cpi, varargsParameter, ParameterBindingFlags.ShouldCoerceType);
16901702
}
16911703
catch (ParameterBindingException pbex)
16921704
{
1693-
// To make all of the following work similarly (the first is handled elsewhere, but second and third are
1694-
// handled here):
1695-
// Set-ClusterOwnerNode -Owners foo,bar
1696-
// Set-ClusterOwnerNode foo bar
1697-
// Set-ClusterOwnerNode foo,bar
1698-
// we make one additional attempt at converting, but only if there is a single argument of type object[].
1699-
if (valueFromRemainingArguments.Count == 1 && valueFromRemainingArguments[0] is object[])
1705+
if (!DefaultParameterBindingInUse)
17001706
{
1701-
cpi.SetArgumentValue(UnboundArguments[0].ArgumentExtent, valueFromRemainingArguments[0]);
1702-
BindParameter(cpi, varargsParameter, ParameterBindingFlags.ShouldCoerceType);
1707+
throw;
17031708
}
17041709
else
17051710
{
1706-
if (!DefaultParameterBindingInUse)
1707-
{
1708-
throw;
1709-
}
1710-
else
1711-
{
1712-
ThrowElaboratedBindingException(pbex);
1713-
}
1711+
ThrowElaboratedBindingException(pbex);
17141712
}
17151713
}
17161714
UnboundArguments.Clear();

test/powershell/engine/ParameterBinding/ParameterBinding.Tests.ps1

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@
228228
[CmdletBinding()]
229229
param (
230230
[array]$Parameter1,
231-
[int[]]$Parameter2
231+
[int[]]$Parameter2
232232
)
233233

234234
Process {
@@ -329,4 +329,98 @@
329329
$result | Should Be $expected
330330
}
331331
}
332+
333+
Context "ValueFromRemainingArguments" {
334+
BeforeAll {
335+
function Test-BindingFunction {
336+
param (
337+
[Parameter(ValueFromRemainingArguments)]
338+
[object[]] $Parameter
339+
)
340+
341+
return [pscustomobject] @{
342+
ArgumentCount = $Parameter.Count
343+
Value = $Parameter
344+
}
345+
}
346+
347+
# Deliberately not using TestDrive:\ here because Pester will fail to clean it up due to the
348+
# assembly being loaded in our process.
349+
350+
if ($IsWindows)
351+
{
352+
$tempDir = $env:temp
353+
}
354+
else
355+
{
356+
$tempDir = '/tmp'
357+
}
358+
359+
$dllPath = Join-Path $tempDir TestBindingCmdlet.dll
360+
361+
Add-Type -OutputAssembly $dllPath -TypeDefinition '
362+
using System;
363+
using System.Management.Automation;
364+
365+
[Cmdlet("Test", "BindingCmdlet")]
366+
public class TestBindingCommand : PSCmdlet
367+
{
368+
[Parameter(Position = 0, ValueFromRemainingArguments = true)]
369+
public string[] Parameter { get; set; }
370+
371+
protected override void ProcessRecord()
372+
{
373+
PSObject obj = new PSObject();
374+
375+
obj.Properties.Add(new PSNoteProperty("ArgumentCount", Parameter.Length));
376+
obj.Properties.Add(new PSNoteProperty("Value", Parameter));
377+
378+
WriteObject(obj);
379+
}
380+
}
381+
'
382+
383+
Import-Module $dllPath
384+
}
385+
386+
AfterAll {
387+
Get-Module TestBindingCmdlet | Remove-Module -Force
388+
}
389+
390+
It "Binds properly when passing an explicit array to an advanced function" {
391+
$result = Test-BindingFunction 1,2,3
392+
393+
$result.ArgumentCount | Should Be 3
394+
$result.Value[0] | Should Be 1
395+
$result.Value[1] | Should Be 2
396+
$result.Value[2] | Should Be 3
397+
}
398+
399+
It "Binds properly when passing multiple arguments to an advanced function" {
400+
$result = Test-BindingFunction 1 2 3
401+
402+
$result.ArgumentCount | Should Be 3
403+
$result.Value[0] | Should Be 1
404+
$result.Value[1] | Should Be 2
405+
$result.Value[2] | Should Be 3
406+
}
407+
408+
It "Binds properly when passing an explicit array to a cmdlet" {
409+
$result = Test-BindingCmdlet 1,2,3
410+
411+
$result.ArgumentCount | Should Be 3
412+
$result.Value[0] | Should Be 1
413+
$result.Value[1] | Should Be 2
414+
$result.Value[2] | Should Be 3
415+
}
416+
417+
It "Binds properly when passing multiple arguments to a cmdlet" {
418+
$result = Test-BindingCmdlet 1 2 3
419+
420+
$result.ArgumentCount | Should Be 3
421+
$result.Value[0] | Should Be 1
422+
$result.Value[1] | Should Be 2
423+
$result.Value[2] | Should Be 3
424+
}
425+
}
332426
}

0 commit comments

Comments
 (0)