简体   繁体   中英

Create PowerShell Cmdlets in C# - Pipeline chaining

I have some classes in C# which I would like to use them in pipelines, I've seen articles about it but I haven't been able to do it yet.

Here's how I am using it right now:

$suite = [MyProject.SuiteBuilder]::CreateSuite('my house')

$houseSet = $suite.AddSet('doors', 'These represents doors')
$houseSet.AddOption('blue', 'kitchen')
$houseSet.AddOption('black', 'bedreoom')
$houseSet.AddOption('white', 'toilet')

And I want to be able to use it like this with pipelines:

$suite = [MyProject.SuiteBuilder]::CreateSuite('my house')

$suite | AddSet('doors', 'These represents doors') `
       | AddOption('blue', 'kitchen') `
       | AddOption('black', 'bedreoom') `
       | AddOption('white', 'toilet')

Here are my C# classes:

//SuiteBuilder.cs
public static class SuiteBuilder
{
    public static Suite CreateTestSuite(string name)
    {
        return new Suite(name);
    }
}

//Suite.cs
public class Suite : PSCmdlet
{
    public string Name { get; set; }
    public IEnumerable<Set> Sets { get; set; }

    public Suite(string name)
    {
        Name = name;
        Sets = new List<Set>();
    }

    // call this method in pipeline
    public Set AddSet(string type, string description)
    {
        Sets.Add(new Set(type, description));
        return Sets.Last();
    }
}


//Set.cs
public class Set : PSCmdlet
{
    public string Type { get; set; }
    public string Description { get; set; }
    public IEnumerable<Option> Options { get; set; }

    public Set(string type, string description)
    {
        Type = type;
        Description = description;
        Options = new List<Option>();
    }

    // call this method from pipeline
    public Set AddOption(string color, string place)
    {
        Options.Add(new Option(color, place));
        return this;
    }
}


//option.cs
public class Option : PSCmdlet
{
    public string Color { get; set; }
    public string Place { get; set; }

    public Option(string color, string place)
    {
        Color = color;
        Place = place;
    }
}

And I am struggling to make these function available to call in the pipeline form.

I also added a comment like call this method in pipeline before each comment that I need to call.

In short, you need to:

  • Accept parameter from pipeline by using [Parameter(ValueFromPipeline =true)]
  • Provide output to pipeline by calling WriteObject method in process method

Detailed Step by Step answer

In this post I'll refactor your code a bit and will show you how you can create Powershell Cmdlet in C# and how to define parameters , accept parameter from pipeline and provide output to pipeline . Then you can easily write something like:

$suite = [MyCmdLets.Suite]::New("suite1")
$suite | Add-Set "type1" "desc1"`
       | Add-Option "color1" "place1"`
       | Add-Option "color2" "place2" | Out-Null

To do so, follow these steps:

  1. Create a C# class Library Project (for example name it MyCmdlets )
  2. Install Package Microsoft.PowerShell.5.ReferenceAssemblies
  3. Create your model classes, independent from PowerShell. (See the code at the bottom of post)
  4. Create the cmdlets considering the following notes: (See the code at the bottom of post)

    • Per each cmdlet, create a C# class
    • Derive from Cmdlet class
    • Decorate the class with CmdletAttribute attribute specifying the verb and the name after verb, for example if you want to have Add-Set , use [Cmdlet(VerbsCommon.Add, "Set")] .
    • If you want to have an output for pipeline, decorate the class with OutputTypeAttribute attribute specifying the type of output, for example if you want to have an output of type Set for the pipeline, use [OutputType(typeof(Set))] .
    • Per each input parameter of your cmdlet, define a C# property.
    • Decorate each parameter property by Parameter attribute.
    • If you want to accept a parameter from pipeline, when decorating with ParameterAttribute attribute, set ValueFromPipeline to true, fro example [Parameter(ValueFromPipeline =true)
    • To provide output to pipeline, override pipeline processing methods like ProcessRecord and using WriteObject write the to output.
  5. Build the project.

  6. Open PowerShell ISE and run the following code:

     Import-Module "PATH TO YOUR BIN DEBUG FOLDER\MyCmdlets.dll" $suite = [MyCmdLets.Suite]::New("suite1") $suite | Add-Set "type1" "desc1"` | Add-Option "color1" "place1"` | Add-Option "color2" "place2" | Out-Null

    It will create a structure like this:

     Name Sets ---- ---- suite1 {MyCmdlets.Set} Type Description Options ---- ----------- ------- type1 desc1 {MyCmdlets.Option, MyCmdlets.Option} Color Place ----- ----- color1 place1 color2 place2

Sample Code

Model Classes

As mentioned above, design your model classes independent from PowerShell like this:

using System.Collections.Generic;
namespace MyCmdlets
{
    public class Suite
    {
        public string Name { get; set; }
        public List<Set> Sets { get; } = new List<Set>();
        public Suite(string name) {
            Name = name;
        }
    }
    public class Set
    {
        public string Type { get; set; }
        public string Description { get; set; }
        public List<Option> Options { get; } = new List<Option>();
        public Set(string type, string description) {
            Type = type;
            Description = description;
        }
    }
    public class Option 
    {
        public string Color { get; set; }
        public string Place { get; set; }
        public Option(string color, string place) {
            Color = color;
            Place = place;
        }
    }
}

CmdLet Classes

Also design the cmdlet classes based on notes which I describe above:

using System.Management.Automation;
namespace MyCmdlets
{
    [Cmdlet(VerbsCommon.Add, "Set"), OutputType(typeof(Set))]
    public class AddSetCmdlet : Cmdlet
    {
        [Parameter(ValueFromPipeline = true, Mandatory = true)]
        public Suite Suite { get; set; }
        [Parameter(Position = 0, Mandatory = true)]
        public string Type { get; set; }
        [Parameter(Position = 1, Mandatory = true)]
        public string Description { get; set; }
        protected override void ProcessRecord() {
            var set = new Set(Type, Description);
            Suite.Sets.Add(set);
            WriteObject(set);
        }
    }

    [Cmdlet(VerbsCommon.Add, "Option"), OutputType(typeof(Option))]
    public class AddOptionCmdlet : Cmdlet
    {
        [Parameter(ValueFromPipeline = true, Mandatory = true)]
        public Set Set { get; set; }
        [Parameter(Position = 0, Mandatory = true)]
        public string Color { get; set; }
        [Parameter(Position = 1, Mandatory = true)]
        public string Place { get; set; }
        protected override void ProcessRecord() {
            var option = new Option(Color, Place);
            Set.Options.Add(option);
            WriteObject(Set);
        }
    }
}

You can use ValueFromPipeline = $true. However, you will have to reference the type variable, and return the item if you want to continue the pipeline. I don't know of a way to work around this. Since it will return, you would have to add an Out-Null at the end to prevent it from hitting console.

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ref?view=powershell-6

function Add-Option {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ref]$Item,
        [Parameter(Mandatory = $true, Position = 0)]
        [String]$Color
        [Parameter(Mandatory = $true, Position = 1)]
        [String]$Room
    )
    $Item.Value.AddOption($Color,$Room)
    return $Item
}

$suite = [MyProject.SuiteBuilder]::CreateSuite('my house')

[ref]$suite | Add-Option 'blue' 'kitchen' `
            | Add-Option 'black' 'bedroom' `
            | Out-Null

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM