过去几天,我正在用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;
}
}
}
答案 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 的情况下已经可以进行了——除非您正在按照我的意见建议和收集将它们全部放入一个列表中,并输出列表