Code:
add-type #"
public interface IFoo
{
void Foo();
}
public class Bar : IFoo
{
void IFoo.Foo()
{
}
}
"# -Language Csharp
$bar = New-Object Bar
($bar -as [IFoo]).Foo() # ERROR.
Error:
Method invocation failed because [Bar]
doesn't contain a method named 'Foo'.
You can do something like
$bar = New-Object Bar
[IFoo].GetMethod("Foo").Invoke($bar, #())
You get (the reflection representaion of) the member of IFoo from the Type object and call an Invoke overload. Too bad one has to do it that way, though.
Similar approach for explicitly implemented properties etc.
If the method takes arguments, they go in the array #() after the comma in the code above, of course.
I wrote something for PowerShell v2.0 that makes it easy to call explicit interfaces in a natural fashion:
PS> $foo = get-interface $bar ([ifoo])
PS> $foo.Foo()
See:
http://www.nivot.org/2009/03/28/PowerShell20CTP3ModulesInPracticeClosures.aspx (archived here).
It does this by generating a dynamic module that thunks calls to the interface. The solution is in pure powershell script (no nasty add-type tricks).
-Oisin
Bad news: It's a bug.
https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=249840&SiteID=99
Related
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()
Does anyone have an example of mocking a dot-sourced class function with Pester 5 and PowerShell 7?
Thank you.
Edit: example
Classes\MyClass.ps1:
class MyClass {
[void] Run() {
Write-Host "Class: Invoking run..."
}
}
MyModule.psm1:
# Import classes
. '.\Classes\MyClass.ps1'
# Instantiate classes
$MyClass = [MyClass]::new()
# Call class function
$MyClass.Run()
Pester only mocks commands - not classes or their methods.
The easiest way to "mock" a PowerShell class for method dispatch testing is by taking advantage of the fact that PowerShell marks all methods virtual, thereby allowing derived classes to override them:
class MockedClass : MyClass
{
Run() { Write-host "Invoking mocked Run()"}
}
The nice thing about this approach is that functions that constrain input to the MyClass type will still work with the mocked type:
function Invoke-Run
{
param([MyClass]$Instance)
$instance.Run()
}
$mocked = [MockedClass]::new()
Invoke-Run -Instance $mocked # this still works because [MockedClass] derives from [MyClass]
Does anyone have an example of mocking a dot-sourced class function
with Pester 5 and PowerShell 7?
You can look at this repo:
https://github.com/dsccommunity/SqlServerDsc/blob/8dde54df19ccbdb95629ec1c074e7a97acf229d2/tests/Unit/Classes/ResourceBase.Tests.ps1#L111-L165
Also, see my answer here which contains example code:
"Unable to find type" when mocking class in a Powershell unit test
Can I overload the PowerShell inbuilt class's methods? If yes then how? Any code sample would be great.
Essentially, I am trying to overload the Equals method of a Hashtable Dictionary PowerShell object to do appropriate comparisons.
I know that you can overload a cmdlet or a function in PowerShell and whatever is the latest definition that is taken up. But I am trying to overload not a cmdlet or a function, but trying to overload inbuilt method for existing class. I have already checked this post: Function overloading in PowerShell
Well, you can overload it from C# code in this manner:
$Source = #"
class MyHashTable : HashTable
{
public override Equals(...) { ... }
}
"#
Add-Type -TypeDefinition $Source -Language CSharp
$x = New-Object MyHashTable ...
I need to create a .NET object in PowerShell. The signature for the constructor is:
public ObjCTor(TClient param1, Func<TClient, IInterface> param2)
and the class is defined as:
public class ObjCTor<TClient> : IOtherInt1, IOtherInt2 where TClient : ConcreteBase
The first parameter is easy enough. I'm stumped on the second. How can I pass a PowerShell function as Func<T1, T2>?
UPDATED
PowerShell can convert a ScriptBlock to a delegate, so something like this might work fine:
$sb = { param($t1) $t1 }
New-Object "ObjCTor[CClient, Func[CClient,IInterface]]" -ArgumentList $param1,$sb
Note that PowerShell cannot deduce generic type arguments from a ScriptBlock. In your case, ObjCTor looks like a generic class so type deduction isn't a factor, but if you were calling a generic method, things could get messy.
The syntax supplied by Jason wasn't quiet correct. This works:
$= New-Object "ObjCTor[TClient]" -ArgumentList $param1,$functionToCall
I was also missing the $args[0] as a parameter in my scriptblock/delegate. This is what I should've had:
$functionToCall = { Get-Blah $args[0] }
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).