Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection with PowerShell

Is it possible to use Dependency Injection (DI) with Windows PowerShell?

My intitial experiments suggest that it isn't. If I attempt to use Constructor Injection in a CmdLet it doesn't even register itself. In other words, this is not possible:

[Cmdlet(VerbsDiagnostic.Test, "Ploeh")]
public class PloehCmdlet : Cmdlet
{
    public PloehCmdlet(IFoo foo)
    {
        if (foo == null)
        {
            throw new ArgumentNullException("foo");
        }

        // save foo for later use
    }

    protected override void ProcessRecord()
    {
        this.WriteObject("Ploeh");
    }
}

If I add a default constructor, the CmdLet can be registered and used, but without the default constructor, it's simply not available.

I know I could use a Service Locator to retrieve my dependencies, but I consider that an anti-pattern so don't want to go that way.

I would have hoped that the PowerShell API had some kind of 'Factory' hook similar to WCF's ServiceHostFactory, but if there is, I can't find it.

like image 495
Mark Seemann Avatar asked Dec 22 '09 10:12

Mark Seemann


3 Answers

An instance of the cmdlet class is created from an empty constructor every time the cmdlet is used in PowerShell. You have no control over which constructor PowerShell will pick, so you cannot do what you are proposing in a straightforward way (and I really have a hard time imagining why you would want to). So the simple answer to this question is NO.

To acheive a similar affect, you can make an interface that looks like a cmdlet (has BeginProcessing/EndProcessing/ProcessRecord/StopProcessing) and use to populate a bunch of cmdlets that are thin wrappers over the real code. IMHO this would be an overcomplicated approach.

I really don't see why you are trying to do this. Could you explain a little more about the scenario?

like image 137
Start-Automating Avatar answered Nov 07 '22 14:11

Start-Automating


Using PSCmdlet as a base class requires a RunSpace to execute and only lets you specify the command to execute as a string. See this link for an example.

I switched back to Cmdlet as a base class and used Property injection to set the dependencies. Not the cleanest solution, but it worked for me. The nice thing about Cmdlet as a base class is that you can invoke it directly from the unit test like this:

        var cmdlet = new MyCmdlet {
                             Dependency = myMockDependencyObject
                         };

        var result = cmdlet.Invoke().GetEnumerator();

        Assert.IsTrue(result.MoveNext());
like image 21
MvdD Avatar answered Nov 07 '22 12:11

MvdD


To Expand on Start-Automating answer:

Essentially you will have your IView interface that would define the operations for the PSCmdlet (WriteObject, WriteError, WriteProgress, and so on) you will have your implementation of that View which would be the actual Commandlet.

Also you will have a Controller, which is the Actual functionallity. On the constructor the Controller receives a IProvider (Which is the one that you want to mock) and an IView. the provider performs the call to the provider and writes the results ot the IView, which will reflect on the IView (Powershell Commandlet).

During the initialization of the View you will create a Controller, pass along itself (the IView) and a Provider, and then it will perform the operation against the controller.

With this approach your Cmdlet is a thin layer that doesn't perform any business logic, and everything is on your controller, which is a component that is testable.

like image 2
Bongo Sharp Avatar answered Nov 07 '22 14:11

Bongo Sharp