Search code examples
.netpowershellpowershell-4.0

Overloaded method resolution bug in Powershell 4


I'm trying to call TfvcHttpClient.GetItemsAsync() from a Powershell script:

$Full = [Microsoft.TeamFoundation.SourceControl.WebApi.VersionControlRecursionType]::Full
$None = [System.Threading.CancellationToken]::None
$Items = $Cli.GetItemsAsync($TFVCPath, $Full, $false, $null, $null, $None).GetAwaiter().GetResult()

In Powershell 4, it errors out:

##[error]System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Management.Automation.Adapter.CompareOverloadCandidates(OverloadCandidate candidate1, OverloadCandidate candidate2, Object[] arguments)
   at System.Management.Automation.Adapter.FindBestCandidate(IEnumerable`1 candidates, Object[] arguments)
   at System.Management.Automation.Adapter.FindBestCandidate(IEnumerable`1 candidates, Object[] arguments, PSMethodInvocationConstraints invocationConstraints)
   at System.Management.Automation.Adapter.FindBestMethod(MethodInformation[] methods, PSMethodInvocationConstraints invocationConstraints, Object[] arguments, String& errorId, String& errorMsg, Boolean& expandParamsOnBest)
   at System.Management.Automation.Language.PSInvokeMemberBinder.InvokeDotNetMethod(DynamicMetaObject target, DynamicMetaObject[] args, BindingRestrictions restrictions, MethodInformation[] mi, Type errorExceptionType)
   at System.Management.Automation.Language.PSInvokeMemberBinder.FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
   at System.Dynamic.DynamicMetaObject.BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
   at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
   at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute7[T0,T1,T2,T3,T4,T5,T6,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
   at System.Management.Automation.Interpreter.DynamicInstruction``8.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

Looks like the logic of overload resolution is buggy. The method is indeed overloaded, but there's only one overload with 6 arguments. The other two have 7.

Powershell 4 supports omitting method parameters with default values (as long as those are null), so overload resolution logic still might apply. However, my invokation is not ambiguous - there's no other overload that takes a VersionControlRecursionType as the second parameter.

Any ideas how to work around this? Maybe there's a way to tell Powershell to call a specific overload?

They've fixed this in PS5, but still...


Edit: reproduced on a clean DLL. Here's the DLL code, reproducing the offending method signatures:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.TeamFoundation.SourceControl.WebApi
{
    public enum VersionControlRecursionType
    {
        None = 0, OneLevel = 1, OneLevelPlusNestedEmptyFolders = 4, Full = 120
    }
    public class TfvcItem { }
    public class TfvcVersionDescriptor { }

    public abstract class TfvcHttpClientBase
    {
        public TfvcHttpClientBase() { }

        public virtual Task<List<TfvcItem>> GetItemsAsync(string project, string scopePath = null, VersionControlRecursionType? recursionLevel = null, bool? includeLinks = null, TfvcVersionDescriptor versionDescriptor = null, object userState = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            return null;
        }
        public virtual Task<List<TfvcItem>> GetItemsAsync(Guid project, string scopePath = null, VersionControlRecursionType? recursionLevel = null, bool? includeLinks = null, TfvcVersionDescriptor versionDescriptor = null, object userState = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            return null;
        }
        public virtual Task<List<TfvcItem>> GetItemsAsync(string scopePath = null, VersionControlRecursionType? recursionLevel = null, bool? includeLinks = null, TfvcVersionDescriptor versionDescriptor = null, object userState = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            return null;
        }
    }


    public class TfvcHttpClient : TfvcHttpClientBase
    {
        public TfvcHttpClient() { }
    }
}

And here's the Powershell to demonstrate the behavior:

Add-Type -Path "Foo.dll"
$Cli = New-Object Microsoft.TeamFoundation.SourceControl.WebApi.TfvcHttpClient
$Full = [Microsoft.TeamFoundation.SourceControl.WebApi.VersionControlRecursionType]::Full
$None = [System.Threading.CancellationToken]::None
$Items = $Cli.GetItemsAsync("", $Full, $false, $null, $null, $None)

I've caught the exception in a debugger, too. The offending method sits in System.Management.Automation.dll. The candidate arguments that describe overloaded methods contain two arrays - arguments and conversionRanks. The code expects them to have the same length, but the former has 7 elements and the latter has 6.


Solution

  • The 7 parameter method overload that takes a GUID as the first parameter doesn't error out in this particular way. Powershell 4 still has a method resolution bug :)