Does PowerShell provide a way to create function annotations? - powershell

EDITED to reduce confusion on the intended aim
Is it possible to create any sort of annotation which can apply a common pattern to a function?
An example may be a standard error-handling routine. Rather than having to implement the same try/catch structure with the error-handling function, it would be nice to have an attribute or annotation with which I could decorate the function, thus implementing the common pattern without using the same explicit code in each function.
Perhaps this is a case of trying to shoehorn PowerShell into doing something it isn't meant to do, but I'd like to avoid some of the repetition I find most common in my scripts.

You just need to inherit from ValidateArgumentsAttribute class and provide custom validation logic in Validate method:
C#:
Add-Type #‘
using System;
using System.Management.Automation;
public class ValidateIsEvenAttribute : ValidateArgumentsAttribute {
protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) {
if(LanguagePrimitives.ConvertTo<int>(arguments)%2==1) {
throw new Exception("Not even");
}
}
}
’#
PowerShell v5:
class ValidateIsOddAttribute : Management.Automation.ValidateArgumentsAttribute {
[void] Validate([object] $arguments, [Management.Automation.EngineIntrinsics] $engineIntrinsics) {
if($arguments%2-eq0) {
throw 'Not odd'
}
}
}
Then you can apply that attributes to function parameters:
function f { param([ValidateIsEven()]$i, [ValidateIsOdd()] $j) $i, $j }
f 2 1 #OK
f 2 2 #Error
f 1 1 #Error

Related

Mocking class functions with Pester 5 and PowerShell 7

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

Powershell: Inherited classes calling Parent's empty constructors, even when passed objects

In powershell 5 I'm running into a strange inheritance problem with classes.
I want to enforce that we are passed an object during setup like [class]::new($mailbox_object), and I was intending to do this by causing [class]::new() to throw an error if it's associated object isn't assigned (say by a child constructor).
But powershell is calling empty parent constructors BEFORE calling the child constructor that was passed the object and I can't figure out if this is a bug or expected, and more importantly how to enforce that we have to be given an object at creation time
Design pattern speak: I'm trying to implement what I call a Unified Interface pattern, which is a Facade pattern to simplify/unify interactions with similar but differently typed objects, where actions for those objects are selected using a Strategy pattern and the the strategy is chosen automatically by the Facade when created (currently by trying to use an invisible Factory hidden within the Facade)
IRL Example: trying to create a unified interface for Exchange Mailbox/Group objects, and implement a MemberOf function (to return which groups it's a member of). But Mailboxes and Groups use different commands (despite matching functionality) AND 365 and On Premises versions also use different commands (get-unifiedgroup instead of get-distributiongroup) so I'm trying to hide that complexity behind a unified Facade for clarity and usability
I'm open to changing my approach, particularly if there's a better way to do this. Just keep in mind there will be at minimum the following types of disparate objects each of which will need their own implementation of .MemberOf(): Interface_Mailbox_365, Interface_Mailbox_OnPremises, Interface_Group_365, Interface_Group_OnPremises, and I may implement Offline and Generic versions eventually.
MRE below, lines with > are the output. Since I've narrowed it to the an issue with the Interface creation, I've not included the Facade or Factory, but I can add them if they end up being needed.
class Interface_MailObject
{
$MailObject = "Interface_MailObject class - initial"
Interface_MailObject(){write-warning "Interface_MailObject::new() MailObject: {$($this.MailObject)}"}
static [Interface_MailObject] Build($object)
{
if
($object -eq "Mailbox Standin")
{return [Interface_Mailbox_365]::new($object)}
else
{throw("we don't reach here")}
}
}
Class Interface_Mailbox : Interface_MailObject
{
$MailObject = "Interface_Mailbox class - initial"
Interface_Mailbox () {write-warning "Interface_Mailbox::new() MailObject: {$($this.MailObject)}"}
Interface_Mailbox ($MailObject) {$this.MailObject = "Interface_Mailbox class - {$($MailObject)}"}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
$MailObject = "Interface_Mailbox_365 class - initial"
Interface_Mailbox_365 () {write-warning "Interface_Mailbox_365::new() MailObject: {$($this.MailObject)}"}
Interface_Mailbox_365 ($MailObject) {$this.MailObject = "Interface_Mailbox_365 class - {$($MailObject)}"}
[object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}
[Interface_MailObject]::new("Mailbox Standin")|tee -va a
> WARNING: Interface_MailObject::new() MailObject: {Interface_Mailbox_365 class - initial}
> WARNING: Interface_Mailbox::new() MailObject: {Interface_Mailbox_365 class - initial}
>
> MailObject
> ----------
> Interface_Mailbox_365 class - {Mailbox Standin}
Notice that even though we called [Interface_Mailbox_365]::new("Mailbox Standin") powershell executed the grandparent's empty constructor, then the parent's empty constructor, before running the one we called.
If they executed in the other order, it would be fine. If they called parent constructors that match the same parameter qty and type, that would also be fine
But it's doing neither, and I don't know how to resolve it without using some weird acrobatics with a Singleton factory which seems like an excessive amount of micromanagement for something that should be a common need (requiring an input parameter during initialization) so I'm guessing I'm overlooking something
TL:DR
Use child(object):base(object){} to declare the constructor instead of child(object){}
Thanks to #Mathias R. Jessen for helping me work it out.
At first I thought I had to decouple the Facade/Factory from the Template, rather than being able to have them be the same class. Unfortunately this would mean I'm calling [MailObject_Interface]::Build($object) but not returning a [MailObject_Interface] type.
After doing some research I realized what Mathias was saying is a child constructor child(object){} is inferred to mean child(object):base(){} and you can override this by explicitly stating child(object):base(object){}
Paring that with an additional piece to verify the parent isn't called directly I was able to achieve success
Class MailObject_Interface
{
[string] $MailObject
MailObject_Interface ()
{throw("You must call ::Build(`$object), because we return specialized types based on the mail object")}
MailObject_Interface ($object) {[MailObject_Interface]::new()} # this triggers the error above
MailObject_Interface ($object, $codephrase)
{
Write-Warning "calling MailObject_Interface::New($($object), $($codephrase)) {$($this.MailObject)}"
# the Codephrase ensures
# either we're being called from one of our children,
# or whomever calls us is aware of our internal workings and is taking responsibility for making sure we're handled correctly
if
($codephrase -eq "Shazam!")
{$this.MailObject = $object}
else
{[MailObject_Interface]::new()} # this triggers the error above
}
# We run through ::Build instead of ::New because we want to return a child typed object rather than ourselves
static [MailObject_Interface] Build($object)
{
if
($object -eq "Mailbox Standin")
{return [Interface_Mailbox_365]::new($object)}
else
{throw("we don't reach here")}
}
}
Class Interface_MailObject_Template : MailObject_Interface
{
Interface_MailObject_Template ($object) : base ($object, "Shazam!") {Write-Warning "calling Interface_MailObject_Template::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw(".MemberOf will be type+context specific")}
}
Class Interface_Mailbox : Interface_MailObject_Template
{
Interface_Mailbox ($object) : base ($object) {Write-Warning "calling Interface_Mailbox::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw("Mailbox.MemberOf will be context specific")}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
Interface_Mailbox_365 ($object) : base ($object) {Write-Warning "calling Interface_Mailbox_365::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}
#\/ Rough Tests \/#
# should succeed
function Test_Correct()
{
Try
{
[MailObject_Interface]$a = [MailObject_Interface]::Build("Mailbox Standin")
return "Succeded ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_New_WithObject_MissingCodephrase()
{
Try
{
$a = [MailObject_Interface]::New("Mailbox Standin")
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_EmptyBuild()
{
Try
{
$a = [MailObject_Interface]::Build()
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_EmptyNew()
{
Try
{
$a = [MailObject_Interface]::New()
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
"$(Test_Correct):`tTest_Correct (should have succeeded)"
"$(Test_New_WithObject_MissingCodephrase):`tTest_New_WithObject_MissingCodephrase (should have failed)"
"$(Test_EmptyBuild):`tTest_EmptyBuild (should have failed)"
"$(Test_EmptyNew):`tTest_EmptyNew (should have failed)"
And here are the test results
> WARNING: calling MailObject_Interface::New(Mailbox Standin, Shazam!) {}
> WARNING: calling Interface_MailObject_Template::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox_365::New(Mailbox Standin) {Mailbox Standin}
> Succeded (Interface_Mailbox_365): Test_Correct (should have succeeded)
> Failed: Test_New_WithObject_MissingCodephrase (should have failed)
> Failed: Test_EmptyBuild (should have failed)
> Failed: Test_EmptyNew (should have failed)
Also sorry about the unusual format in the Testing functions. I forgot to convert the bracket indenting to the normal standard, I use a nonstandard approach that I find more functional because it frames control logic with whitespace making it more naturally follow the perimeter walk your eyes do when skimming

Powershell - Optional parameter in method of a class

In powershell functions i can have something like
Function myFunction {
Param(
[Parameter(Mandatory=$True)][string]$foo,
[Parameter(Mandatory=$False)][string]$bar = "whatever"
)
....
}
But this seems limited to functions - is there something similar for method?
class MyClass {
...
[void]MethodA {
Param(
....
dont works for me.
The interpreter complains about the missing '(' in the class method parameter list.
Adding methods to classes works just like in most other scripting languages one might know. Instead of [void]MethodA {Param()...} you might want to add a block like described in this blog post or here:
class MyClass {
#...
[void]MethodA ($param) {
#...
}
}
As your title says optional parameters (but your question doesn't) a short word on that...
Usually you want multiple signatures for such cases. That means you create a method MethodA($arg1, $arg2) and a delegating method like MethodA($arg1) {MethodA($arg1, $null)}

How to use delegates inside a static PowerShell class?

I want to use a func delegate in my static PowerShell 5.0 class:
I had issues to find a way to assign other static class methods for my delegate.
This code is working but not very convinient.
Is there a better way to use a delegate here ?
And I need to instanciate my static! class, only to get the type.
I tried the outcommented line, how you would do it with .NET types, but it's not working for my own class.
How can I get the type of my static class here more elegant ?
And, BTW, GetMethod() did not accecpt the BindingFlags parameter, why ?
class Demo
{
hidden static [object] Method_1([string] $myString)
{
Write-Host "Method_1: $myString"
return "something"
}
hidden static [object] Method_2([string] $myString)
{
Write-Host "Method_2: $myString"
return $null
}
hidden static [object] TheWrapper([string]$wrappedMethod, [string] $parameter)
{
# do a lot of other stuff here...
#return [System.Type]::GetType("Demo").GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
return [Demo]::new().GetType().GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
}
static DoWork()
{
Write-Host ([Demo]::TheWrapper('Method_1', 'MyMessage'))
[Demo]::TheWrapper('Method_2', 'c:\my_file.txt')
}
}
[Demo]::DoWork()
You don't need to create an instance of [demo] since [demo] is the actual type of the class. Also, you can write the delegate type more simply as [Func[string,object]]. This simplifies the body of TheWrapper method to
return [Demo].GetMethod($wrappedMethod).CreateDelegate([Func[string, object]]).Invoke($parameter)
but a much simpler way to do this in PowerShell is to get the method by passing its name to the '.' operator then invoking the result:
return [demo]::$wrappedMethod.Invoke($parameter)
In PowerShell, the right-hand side of the '.' operator doesn't need to be a constant. You can use an expression that results in the name of the method (or property) to retrieve.

How can I call explicitly implemented interface method from PowerShell?

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