I would just like to get the validation arguments provided by an IValidateSetValuesGenerator class similar to a return when we use a bad argument (see example command Sample -Verb 'BadVerb')
Below is an example of code:
class verb : System.Management.Automation.IValidateSetValuesGenerator
{
[String[]] GetValidValues()
{
[System.Collections.ArrayList]$Verbs = #()
$VerbsSource = Get-Verb
foreach ($Verb in $VerbsSource)
{
$Verbs.Add([PSCustomObject]#{'verb' = $Verb.Verb})
}
return ($Verbs).Verb
}
}
function Sample
{
[CmdletBinding(SupportsShouldProcess=$true,
PositionalBinding=$false,
ConfirmImpact='Medium')]
[Alias()]
[OutputType([bool])]
Param
(
[Parameter(Mandatory=$true)]
[ValidateSet([verb], ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
[string]$Verb
)
Begin
{
}
Process
{
}
End
{
return $Verb
}
}
Sample -Verb 'BadVerb'
Sample -Verb 'Get'
Mathias R. Jessen has provided the crucial pointer:
# PSv5+ syntax:
# Construct (create an instance of) the [verb] class and call its
# .GetValidValues() instance method.
[verb]::new().GetValidValues() # Returns a [string[]] array of valid values.
Your [verb] class implements the [System.Management.Automation.IValidateSetValuesGenerator] interface (for use in [ValidateSet] attributes to allow constraining parameter values to a dynamically generated set of valid (permissible) values).
This interface, has a single instance method, .GetValidValues(), which returns the permissible values, and which PowerShell calls behind the scenes during parameter validation.
Therefore, in order to call this method yourself, you need to create an instance of your [verb] class first:
In PowerShell v5+, the best choice is to use the static ::new() method, which is PowerShell's way of exposing public constructors; that is, [verb]::new() is equivalent to new verb() in C#.
In older PowerShell versions you must use the New-Object cmdlet for calling constructors; the equivalent of [verb]::new() is New-Object verb
# PowerShell v5+
[verb]::new().GetValidValues()
# PowerShell v4-, but also works in higher versions.
(New-Object verb).GetValidValues()
Syntax pitfalls:
::new() uses method syntax (as in C#), whereas New-Object, as a cmdlet, uses command syntax,[1] i.e. is invoked like a shell command: no (...) around the list of arguments, whitespace as the argument separator.
The following example - using a constructor with arguments - illustrates the difference:
# PSv5+ ::new() call - method syntax.
# Equivalent of this C# constructor call:
# new Regex("^\w+=.+", RegexOptions.Multiline, new Timespan(1000));
[regex]::new('^\w+=.+', [System.Text.RegularExpressions.RegexOptions]::Multiline, [timespan]:new(1000))
# Equivalent New-Object call - command syntax,
# using verbose *named* parameter binding.
# Note the following:
# * absence of (...) around the list of arguments as a whole
# * use of whitespace to separate arguments (and also parameter names from their arguments)
# * the need to separate the *constructor* arguments with ","
# as they must be passed as an *array*.
# * the need to enclose [System.Text.RegularExpressions.RegexOptions]::Multiline individually in (...)
New-Object -TypeName regex -ArgumentList '^\w+=.+', ([System.Text.RegularExpressions.RegexOptions]::Multiline)
# Equivalent call using *positional* (unnamed) parameter binding.
New-Object regex '^\w+=.+', ([System.Text.RegularExpressions.RegexOptions]::Multiline)
A particular pitfall is when a constructor takes a single argument that is an array or a collection, which with New-Object requires wrapping the array in an aux., transitory array:
$array = 1, 2
# OK: Initialize an ArrayList instance via an array that
# binds as a whole to the `System.Collections.ICollection c`
# constructor parameter.
[System.Collections.ArrayList]::new($array)
# !! BROKEN
# $array is interpreted as *multiple* (two separate) arguments.
New-Object System.Collections.ArrayList -ArgumentList $array
# OK
# Need to wrap the array in an aux. transitory array.
New-Object System.Collections.ArrayList -ArgumentList (, $array)
[1] In PowerShell terms, method syntax is parsed in expression mode, whereas command syntax is parsed in argument mode. See the conceptual about_Parsing help topic.
In addition to mklement0's excellent explanation of [Verb]::new(), allow me to provide you with an example of how you could structure the value generator class so that you don't need an instance to generate the values:
class verb : System.Management.Automation.IValidateSetValuesGenerator
{
[String[]] GetValidValues()
{
return [Verb]::GetValidValues()
}
static [String[]] GetValidValues()
{
[System.Collections.ArrayList]$Verbs = #()
$VerbsSource = Get-Verb
foreach ($Verb in $VerbsSource)
{
$Verbs.Add([PSCustomObject]#{'verb' = $Verb.Verb})
}
return ($Verbs).Verb
}
}
Now we've moved the actual heavy lifting to a static method, and the instance method (which is what PowerShell is going to call) simply calls the static method.
Now you can get the value list without creating an instance of [verb]:
PS ~> [verb]::GetValidValues()
Related
I'm writing a function that's basically a convenience wrapper around an external program that it'll invoke. Several of the parameters will be forwarded to the external program, but not all.
I was writing
$ArgsToFwd = #()
switch ($PSBoundParameters.Keys) {
'--server' {$ArgsToFwd += #('--server',$server)}
'--userid' {$ArgsToFwd += #('--userid',$userid)}
...
}
but then I thought it might be better to define a custom parameter attribute that could let me do something like:
params(
[Parameter()]
[IsExternal()]
[string]$Server
)
#...
foreach ($key in $PSBoundParameters.Keys) {
if (<#a test for the custom [IsExternal] attribute#>) {
$ArgsToFwd += #($key, $PSBoundParameters[$key])
}
}
But I can't quite figure it out. Can that be done?
If you want to use custom attributes, there are 3 things you need to do:
Define a custom Attribute type
Decorate your function's parameters with instances of said attribute
Write a mechanism to discover the attribute decorations and do something with them at runtime
Let's start with step 1, by defining a custom class that inherits from System.Attribute:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
Attribute annotations map directly back to the target attribute's constructor, so in this case, we'll be using it like [ProxyParameter('someValue')] and 'someValue' will then be stored in the $Target property.
Now we can move on to step 2, decorating our parameters with the new attribute. You can omit the Attribute part of the name when applying it in the param block, PowerShell is expecting all annotations to be attribute-related anyway:
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# ... code to resolve ProxyParameter goes here ...
}
For step 3, we need a piece of code that can discover the attribute annotations on the parameters of the current command, and use them to map the input parameter arguments to the appropriate target names.
To discover declared parameter metadata for the current command, the best entry point is $MyInvocation:
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = #()
# Create mapping table for parameter names
$paramNameMapping = #{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
Now that the parameters how been filtered and renamed appropriately, you can invoke the target application with the correct arguments via splatting:
.\externalApp.exe #argumentsToFwd
Putting it all together, you end up with something like:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = #()
# Create mapping table for parameter names
$paramNameMapping = #{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
externalApp.exe #argumentsToFwd
}
You can add other properties and constructor arguments to the attributes to store more/different data (a flag to indicate whether something is just a switch, or a scriptblock that transforms the input value for example).
If you need this for multiple different commands, extract the step 3 logic (discovering attributes and resolving parameter names) to a separate function or encapsulate it in a static method on the attribute class.
In powershell you can make functions with function name {commands} and make those functions take arguments with this:
function myFunction {
param($var1, $var2)
}
but you can also accomplish this with
function myFunction($var1, $var2) {}
and they would be the same.
For example, if I made a function func1 be:
function func1 {
param($var1, $var2)
echo "$var1 $var2"
}
I would call it by using func1 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2.
Input:
PS C:\Users\Neko> func1 1 2
Output:
1 2
However, if I do the same thing but instead I did the other method of passing arguments to functions:
function func2($var1, $var2) {
echo "$var1 $var2"
}
I would also call it the same exact way, calling it by using func2 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2 like the previous function.
Input:
PS C:\Users\Neko> func2 1 2
Output:
1 2
So everything seems the same and constant between the two renditions of the function, so my question is, is there a difference between the two methods of passing arguments to functions or are they both actually the same? Even if it is the most minor of details, or just a parsing difference, I would like to know any differences between the two in functions specifically since param has other uses as well.
UPDATE: The arguments you can do in param like [parameter(Mandatory=$true, ValueFromPipeline=$true)] and [String[]] are not unique to param. You can also accomplish this in the other 'non-param' example by doing:
function func2(
[parameter(Mandatory=$true, ValueFromPipeline=$true, etc)]
[String[]]
$var1, $var2
) {
echo "$var1 $var2"
}
To complement 7cc's helpful answer:
While the two syntax forms are mostly interchangeable when you define a function's parameters, only the param(...) block syntax works in the following circumstances:
If you want to use a [CmdletBinding()] attribute and its properties to (explicitly) make your function or script an advanced function or script.[1]
If you're writing a script file(*.ps1) or script block ({ ... }): the only way to declare parameters for them is is by placing a param(...) block at the beginning.
Therefore, you may opt to always use the param(...) block syntax, for consistency across function and script parameter definitions.
If a [CmdletBinding(...)]) attribute is used, it must directly precede the param(...) block.
As for:
I would call it by using func1(1)(2)
No, you would call it as follows:
func1 1 2
That is, PowerShell functions are called like shell commands: without parentheses, separated by whitespace; while your invocation happens to work too, the use of (...) around the arguments can change their interpretation:
without the enclosing (...) the arguments are parsed in argument mode, where, notably, strings needn't be quoted
with the enclosing (...), are parsed in expression mode, where strings do need to be quoted.
See this answer for more information.
[1] While you can place a [CmdletBinding(...)] attribute inside the parentheses with the function Foo (...) { ... } syntax without provoking an error, doing so is effectively ignored. Separately, in the absence of an (effective) explicit [CmdletBinding(...)] attribute, with either syntax, if you happen to decorate at least one parameter with a [Parameter()] attribute, you get the default behaviors of an advanced function (e.g., support for automatic common parameters such as -Verbose), because using [Parameter()] implicitly makes a function an advanced one (as if a [CmdletBinding()] attribute - without explicit property values - were in effect). However, if you need an explicit [CmdletBinding(...)] attribute, so as to opt into non-default advanced-function behaviors, via property values such as PositionalBinding=$false or SupportsShouldProcess=$true, use of a param(...) block is your only option.
One thing is that the CmdletBinding attribute requires Param
function Echo-Confirm
{
# Here
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
Param ($val=1)
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "Confirmed $val"
}
}
Edit after this comment
The syntax is fine, but CmdletBinding has no effect
Function foo (
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
[Parameter()]$val=1
) {
# never confirm
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "always here"
}
else {
Write-Output "never here"
}
}
foo -Confirm
# throws an error
foo: A parameter cannot be found that matches parameter name 'confirm'.
From About Functions - Functions with Parameters - Named Parameters:
You can define any number of named parameters. You can include a
default value for named parameters, as described later in this topic.
You can define parameters inside the braces using the Param keyword,
as shown in the following sample syntax:
function <name> {
param ([type]$parameter1[,[type]$parameter2])
<statement list>
}
You can also define parameters outside the braces without the Param
keyword, as shown in the following sample syntax:
function <name> [([type]$parameter1[,[type]$parameter2])] {
<statement list>
}
…
While the first method is preferred, there is no difference between
these two methods.
Edit
#mklement0 thanks for helpful explication of the latter (emphasized) statement.
This statement (there is no difference between these two methods) is valid despite of 7cc's improper guesswork.
7cc's answer is right, explained in mklement0's comments below and his updated answer.
In About Functions Advanced Parameters => Attributes of parameters, there are some allusions to the relation of CmdletBinding and Parameter attributes and advanced functions (advanced functions use the CmdletBinding attribute to identify them as functions that act similar to cmdlets):
… if you omit the CmdletBinding attribute, then to be recognized
as an advanced function, the function must include the Parameter
attribute…
… to be recognized as an advanced function, rather than a simple
function, a function must have either the CmdletBinding attribute
or the Parameter attribute, or both.
I can't comprehend PowerShell inventors' motivation for such (confusing for me) design…
I have the powershell function below
Function Test
{
Param
(
[Parameter()]
[string]$Text = "default text"
)
Write-Host "Text : $($Text)"
}
And I would like to be able to call this function like below :
Test -Text : should display the default text on the host
Test -Text "another text" : should display the provided text on the host
My issue is that the first syntax is not allowed in powershell ..
Any ideas of how I can achieve this goal ?
I would like a kind of 'switch' parameter that can take values other than boolean.
Thanks
The problem you're running into is with parameter binding. PowerShell is seeing [string] $Text and expecting a value. You can work around this like so:
function Test {
param(
[switch]
$Text,
[Parameter(
DontShow = $true,
ValueFromRemainingArguments = $true
)]
[string]
$value
)
if ($Text.IsPresent -and [string]::IsNullOrWhiteSpace($value)) {
Write-Host 'Text : <default text here>'
}
elseif ($Text.IsPresent) {
Write-Host "Text : $value"
}
}
Note: this is a hacky solution and you should just have a default when parameters aren't passed.
tl;dr
PowerShell does not support parameters with optional values.
A workaround is possible, but only for a single parameter.
Maximilian Burszley's helpful answer provides a workaround for a single parameter, via a catch-all parameter that collects all positionally passed arguments via the ValueFromRemainingArguments parameter property.
Fundamentally, though, what you're asking for is unsupported in PowerShell:
PowerShell has no support for parameters with optional values as of 7.2 - except for [switch] parameters, which are limited to [bool] values.
That is:
Any parameter you declare with a type other than [switch] invariably requires a value (argument).
The only other option is to indiscriminately collect any unbound positional arguments in a ValueFromRemainingArguments-tagged parameter, but you won't be able to associate these with any particular other bound parameter.
In other words:
If you happen to need just one optional-argument parameter, the ValueFromRemainingArguments can work for you (except that you should manually handle the case of mistakenly receiving multiple values), as shown in Maximilian Burszley's answer.
If you have two or more such parameters, the approach becomes impractical: you'd have to know in which order the parameters were passed (which PowerShell doesn't tell you) in order to associate the remaining positional arguments with the right parameters.
With [switch] parameters (using an imagined -Quiet switch as an example):
The default value - if you just pass -Quiet -is $true.
$false is typically indicated by simply not specifying the switch at all (that is, omitting -Quiet)
However, you may specify a value explicitly by following the switch name with :, followed by the Boolean value:
-Quiet:$true is the same as just -Quiet
-Quiet:$false is typically the same as omitting -Quiet; in rare cases, though, commands distinguish between an omitted switch and one with an explicit $false value; notably, the common -Confirm parameter allows use of -Confirm:$false - as opposed to omission of -Confirm - to override the value of the $ConfirmPreference preference variable.
While : as the separator between the parameter name and its argument (as opposed to the usual space char.) is supported with all parameters, with [switch] parameters it is a must so as to unequivocally signal that what follows is an argument for the switch parameter (which by default needs no argument) rather than an independent, positional argument.
The above tells us that PowerShell already has the syntax for general support of optional-argument parameters, so at some point in the future it could support them with any data type, as suggested in GitHub issue #12104.
I like #Maximilian Burszley's answer (and his name!) for String, I tweaked it for Ints:
function Optional-SwitchValue {
[CmdletBinding()]
param (
[Switch]
$Bump,
[Int]
$BumpAmount
)
Begin {
# nifty pattern lifted from https://stackoverflow.com/questions/58838941/powershell-special-switch-parameter
# default Bump to 1
if ($Bump.IsPresent -and -not $BumpAmount) {
$BumpAmount = 1
}
}
Process {
if($Bump) {
#use $BumpAmount in some way
}
}
}
I have been working with the PowerShell AST to create some custom rules for PSScriptAnalyzer.
In a lot of the example code for AST, there is one line that I don't understand. Here is an example.
First parse a file, in this case, the current open file in the ISE.
$AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::
ParseInput($psISE.CurrentFile.Editor.Text, [ref]$null, [ref]$null)
This makes sense so far. Let's say that we want to look for all the ParameterAst objects. The code that I have seen to do this is below.
$params = $AbstractSyntaxTree.FindAll({$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true)
This line of code is calling FindAll and passing in a scriptblock, that seems to be acting as a filter, so that only ParameterAst objects are returned.
What I don't understand here is how $args[0] fits into this call. How are any parameters actually getting passed into the scriptblock when the FindAll method is invoked?
FindAll method has following signature (from msdn):
public IEnumerable<Ast> FindAll (
Func<Ast,bool> predicate,
bool searchNestedScriptBlocks
)
So first argument is a delegate that takes Ast as input, and returns bool.
In Powershell you can create such delegate like that:
$delegate = { param($ast) $ast -is [System.Management.Automation.Language.ParameterAst] }
Or without declaring parameter:
$delegate = { $args[0] -is [System.Management.Automation.Language.ParameterAst] }
FindAll method will then do something like that (pseudocode):
foreach ($node in $allNodes) {
$shouldAdd = & $delegate $node <-- this is how $node gets passed to your delegate
if ($shouldAdd) {
<add the node to the output list>
}
}
Think of the scriptblock as an anonymous callback function.
It's really the same thing that happens when you use Where-Object { $someCondition }.
.FindAll finds all the (things) and for each one it calls the function you provided it. It's apparently expecting a [bool] result, and returning the objects that satisfied the conditions present in the callback.
In a function or script or scriptblock in powershell, you can have named parameters that are explicitly defined, or you can reference parameters without declaring them using the $args array, which is what's happening here.
Using a scriptblock as a callback is similar to using it for an event:
$Args
Contains an array of the undeclared parameters and/or parameter
values that are passed to a function, script, or script block.
When you create a function, you can declare the parameters by using the
param keyword or by adding a comma-separated list of parameters in
parentheses after the function name.
In an event action, the $Args variable contains objects that represent
the event arguments of the event that is being processed.
For example I have a .NET object $m with the following method overloads:
PS C:\Users\Me> $m.GetBody
OverloadDefinitions
-------------------
T GetBody[T]()
T GetBody[T](System.Runtime.Serialization.XmlObjectSerializer serializer)
If I try to invoke the parameterless method I get:
PS C:\Users\Me> $m.GetBody()
Cannot find an overload for "GetBody" and the argument count: "0".
At line:1 char:1
+ $m.GetBody()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
I understand PowerShell v3.0 is supposed to work more easily with generics. Obviously I need to tell it somehow what type I want returned but I cannot figure out the syntax.
It looks like you are trying to invoke a generic method.
In powershell this can be done by:
$nonGenericClass = New-Object NonGenericClass
$method = [NonGenericClass].GetMethod("SimpleGenericMethod")
$gMethod = $method.MakeGenericMethod([string])
# replace [string] with the type you want to use for T.
$gMethod.Invoke($nonGenericClass, "Welcome!")
See this wonderful blog post for more info and additional examples.
For your example you could try:
$Source = #"
public class TestClass
{
public T Test<T>()
{
return default(T);
}
public int X;
}
"#
Add-Type -TypeDefinition $Source -Language CSharp
$obj = New-Object TestClass
$Type = $obj.GetType();
$m = $Type.GetMethod("Test")
$g = new-object system.Guid
$gType = $g.GetType()
$gm = $m.MakeGenericMethod($gType)
$out = $gm.Invoke( $obj, $null)
#$out will be the default GUID (all zeros)
This can be simplified by doing:
$Type.GetMethod("Test").MakeGenericMethod($gType).Invoke( $obj, $null)
This has been testing in powershell 2 and powershell 3.
If you had a more detailed example of how you came across this generic method I would be able to give more details. I have yet to see any microsoft cmdlets return anything that give you generic methods. The only time this comes up is when custom objects or methods from c# or vb.net are used.
To use this without any parameters you can use Invoke with just the first parameter.
$gMethod.Invoke($nonGenericClass)
Calling a generic method on an object instance:
$instance.GetType().GetMethod('MethodName').MakeGenericMethod([TargetType]).Invoke($instance, $parameters)
Calling a static generic method (see also Calling generic static method in PowerShell):
[ClassType].GetMethod('MethodName').MakeGenericMethod([TargetType]).Invoke($null, $parameters)
Note that you will encounter an AmbiguousMatchException when there is also a non-generic version of the method (see How do I distinguish between generic and non generic signatures using GetMethod in .NET?). Use GetMethods() then:
([ClassType].GetMethods() | where {$_.Name -eq "MethodName" -and $_.IsGenericMethod})[0].MakeGenericMethod([TargetType]).Invoke($null, $parameters)
(Mind that there could be more than one method that match the above filter, so make sure to adjust it to find the one you need.)
Hint: You can write complex generic type literals like this (see Generic type of Generic Type in Powershell):
[System.Collections.Generic.Dictionary[int,string[]]]
To call a (parameterless) generic method with overloads from Powershell v3, as shown in the OP example, use the script Invoke-GenericMethod.ps1 from the reference provided by #Chad Carisch, Invoking Generic Methods on Non-Generic Classes in PowerShell.
It should look something like
Invoke-GenericMethod $m GetBody T #()
This is a verified working code sample that I am using:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Practices.ServiceLocation") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Practices.SharePoint.Common") | Out-Null
$serviceLocator = [Microsoft.Practices.SharePoint.Common.ServiceLocation.SharePointServiceLocator]::GetCurrent()
# Want the PowerShell equivalent of the following C#
# config = serviceLocator.GetInstance<IConfigManager>();
# Cannot find an overload for "GetInstance" and the argument count: "0".
#$config = $serviceLocator.GetInstance()
# Exception calling "GetMethod" with "1" argument(s): "Ambiguous match found."
#$config = $serviceLocator.GetType().GetMethod("GetInstance").MakeGenericMethod([IConfigManager]).Invoke($serviceLocator)
# Correct - using Invoke-GenericMethod
$config = C:\Projects\SPG2013\Main\Scripts\Invoke-GenericMethod $serviceLocator GetInstance Microsoft.Practices.SharePoint.Common.Configuration.IConfigManager #()
$config.CanAccessFarmConfig
Here is an alternate script that I haven't tried but is more recent and being actively maintained, Invoke Generic Methods from PowerShell.
Update: PowerShell (Core) 7.3+ now does support calling
generic methods with explicit type arguments.
Note: As of this writing, only preview versions of 7.3 are available.
# PS v7.3+ only; using [string] as an example type argument.
$m.GetBody[string]()
See the conceptual about_Calling_Generic_Methods help topic
PowerShell (Core) 7.2- and Windows PowerShell:
marsze's helpful answer contains great general information about calling generic methods, but let me address the aspect of calling a parameter-less one specifically, as asked:
As hinted at in the question:
in PSv3+ PowerShell can infer the type from the parameter values (arguments) passed to a generic method,
which by definition cannot work with a parameter-less generic method, because there is nothing to infer the type from.
Prior to PowerShell (Core) 7.3, PowerShell previously had no syntax that would allow you to specify the type explicitly in this scenario.
In such older versions, reflection must be used:
# Invoke $m.GetBody[T]() with [T] instantiated with type [decimal]
$m.GetType().GetMethod('GetBody', [type[]] #()).
MakeGenericMethod([decimal]).
Invoke($m, #())
.GetMethod('GetBody', [type[]] #()) unambiguously finds the parameter-less overload of.GetBody(), due to passing in an empty array of parameter types.
.MakeGenericMethod([decimal]) instantiates the method with example type [decimal].
.Invoke($m, #()) then invokes the type-instantiated method on input object ($m) with no arguments (#(), the empty array).