C#PowerShell模块将PSObject转换为C#类

时间:2018-12-05 00:03:41

标签: c# powershell

过去几天,我正在用C#编写我的第一个PS模块,这对我来说是非常新的。说实话,我在解决这个问题时遇到了一些麻烦(也许咖啡因不够),但是现在我已经提出了解决方案,但是我希望有人可能会有一些更优雅的想法?

我发现有关powershell的一些文档含糊不清,至少在C#中创建模块方面。将C#嵌入到PowerShell中,没问题,在C#中运行powershell代码,还有大量信息,但是有关用C#编写PS模块的信息似乎很少,或者我找错了地方?

chat不休,这是我的情况。首先,我有一个快速又肮脏的新工具,并在下面获取cmdlet示例。

[Cmdlet(VerbsCommon.New, "TestItem")]
[OutputType(typeof(TestItem))]
public class NewItem : Cmdlet
{
    [Parameter(Position = 0)]
    public string FriendlyName
    {
        get { return friendlyname; }
        set { friendlyname = value; }
    }
    private string friendlyname;

    [Parameter(Position = 1)]
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    private string name;

    [Parameter(Position = 2)]
    public int ID
    {
        get { return id; }
        set { id = value; }
    }
    private int id;

    private TestItem item;

    protected override void BeginProcessing()
    {
        item = new TestItem();
    }

    protected override void ProcessRecord()
    {
        item.Name = name;
        item.FriendlyName = friendlyname;
        item.ID = id;
    }

    protected override void EndProcessing()
    {
        WriteObject(item);
    }
}

[Cmdlet(VerbsCommon.Get, "TestItem")]
[OutputType(typeof(TestItem))]
public class GetItem : Cmdlet
{
    [Parameter(Position = 0)]
    public string[] FriendlyName
    {
        get { return friendlyname; }
        set { friendlyname = value; }
    }
    private string[] friendlyname;

    [Parameter(Position = 1)]
    public List<TestItem> Item { get; set; }

    [Parameter(ValueFromPipeline = true)]
    public PSObject InputObject
    {
        set { inputObject = value; }
        get { return inputObject; }
    }
    private PSObject inputObject;

    private List<TestItem> item;

    protected override void BeginProcessing()
    {
        item = new List<TestItem>();
    }

    protected override void ProcessRecord()
    {
        WriteVerbose("processing pipline");

        if (inputObject != null)
        {
            WriteObject(inputObject.ToClassObject<TestItem>());
        }
    }

    protected override void EndProcessing()
    {
        WriteObject(item);
    }
}

然后我有我的快速而肮脏的示例对象类

public class TestItem
{
    public TestItem()
    { }

    public string Name { get; set; }
    public string FriendlyName { get; set; }
    public int ID { get; set; }
}

现在这是我在寻找反馈的地方。从上面我们可以看到new-item创建了一个项目,当通过管道传递给get-item时,它作为PSObject传递。我的目标是将其转换为开始时的类对象,但是我需要能够动态处理Class / type,因为我打算将其用作我正在从事的项目的助手。我真的只是在寻找反馈,因为我觉得这里有一个更优雅的解决方案?

public static class Helper
    {
        public static T ToClassObject<T>(this PSObject pso) where T : class, new()
        {
            try
            {
                T obj = new T();
                foreach (PSPropertyInfo psPropertyInfo in pso.Properties)
                {
                    foreach (var prop in obj.GetType().GetProperties())
                    {
                        try
                        {
                            if (psPropertyInfo.Name == prop.Name)
                            {
                                PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                                propertyInfo.SetValue(obj, Convert.ChangeType(psPropertyInfo.Value, propertyInfo.PropertyType), null);
                            }
                        }
                        catch
                        {
                            continue;
                        }
                    }

                }

                return obj;
            }
            catch
            {
                return null;
            }
        }
    }

2 个答案:

答案 0 :(得分:0)

好吧,您正在从事的工作叫做二进制模块/二进制cmdlet,这大概是一个谷歌搜索词(它给我带来了很多有关此的文章)。您还可以在Powershell Core github上找到许多现实生活中的示例,例如here。 从另一方面来看,这似乎不是为powershell创建cmdlet的一种非常普遍的方式,而且我认为它比纯PS代码要复杂得多。

关于您的问题,据我了解,您的Get-Item cmdlet正在从管道中获取PSObject,因此您需要使用输入属性名称将其转换为自定义类型。如果是这样,您可能可以使用ValueFromPipelineByPropertyName属性代替ToClassObject。我不是C#或二进制模块编写方面的专家,因此我将以纯PS给出示例,我希望将其转换为C#并不是一个大问题

function Get-Column { [CmdletBinding()]param(
    [parameter(ValueFromPipelineByPropertyName=$true)][String]$columnName,
    [parameter(ValueFromPipelineByPropertyName=$true)][String]$type
    )

    Process { Write-Output (New-Object System.Data.DataColumn @($columnName, $type))}
}

$ObjectList = @(
    New-Object psobject -Property @{columnName="FirstName"; type="String"}
    New-Object psobject -Property @{columnName="LastName"; type="String"}
    New-Object psobject -Property @{columnName="Age"; type="Int32"}
)

$ObjectList | Get-Column | Format-Table

因此,cmdlet可以从输入对象中收集参数值,并将其传递给自定义类构造函数。另外,我实际上并不需要Begin和End块,可能它们在您的代码中也是多余的。

答案 1 :(得分:0)

正如 Mike Twc 所说,您正在创建二进制 cmdlet。它们不太经常使用,我个人认为这是一种耻辱,因为我看到很多看起来像 C# 的“PowerShell”脚本,并且二进制 cmdlet 允许在其他 C# 应用程序中使用本机、更强类型的使用。非常感谢您的尝试。

我认为您的主要问题是您指定的类型。为了优雅起见,您真的不需要在编写属性时如此冗长。

所以这是一个更优雅的重写,应该可以解决您的问题:

[Cmdlet(VerbsCommon.New, "TestItem")]
[OutputType(typeof(TestItem))]
public class NewItem : Cmdlet
{
    [Parameter(Position = 0)]
    public string FriendlyName { get; set; }

    [Parameter(Position = 1)]
    public string Name { get; set; }

    [Parameter(Position = 2)]
    public int ID { get; set; }

    protected override void ProcessRecord()
    {
        var item = new TestItem();
        item.Name = Name;
        item.FriendlyName = Friendlyname;
        item.ID = ID;
        WriteObject(item);
    }
}

[Cmdlet(VerbsCommon.Get, "TestItem")]
[OutputType(typeof(TestItem))]
public class GetItem : Cmdlet
{
    // what is the point of this parameter since it is never used?
    // Is it supposed to filter? If so, I would suggest using
    // `Where-Object FriendlyName -In "MyName1","MyName2","MyName3"`
    // instead of trying to write your own...
    [Parameter(Position = 0)]
    public string[] FriendlyName { get; set; }

    // This parameter is unused and, likely, is what you
    // intend for the InputObject parameter
    //[Parameter(Position = 1)]
    //public List<TestItem> Item { get; set; }

    // Whatever this type is, because it is ValueFromPipeline,
    // is what the pipeline input will be converted to
    [Parameter(ValueFromPipeline = true)]
    public TestItem InputObject { get; set; }

    private List<TestItem> items;

    protected override void BeginProcessing()
    {
        items = new List<TestItem>();
    }

    protected override void ProcessRecord()
    {
        WriteVerbose("processing pipline");

        if (InputObject != null)
        {
            // do you actually mean `items.add(InputObject);`?
            WriteObject(InputObject);
        }
    }

    protected override void EndProcessing()
    {
        // this is empty because it never has elements added?
        WriteObject(items, enumerateCollection: false);
    }
}

现在是另一条评论:Get- 动词,您可以在 MS Doc 中看到这一点,应该与 New- 动词类似,因为它已经 em> 在系统中,但不同之处在于它不实例化新实例。我不完全确定您要做什么,因为您似乎将输入对象移交给了管道中的稍后部分,否则它在没有 cmdlet 的情况下已经可以进行了——除非您正在按照我的意见建议和收集将它们全部放入一个列表中,并输出列表