使用 DefaultDisplayPropertySet 从 C# 进行 PowerShell 输出格式化

PowerShell Output Formatting from C# using DefaultDisplayPropertySet

我正在用 C# 编写 PowerShell cmdlet。我知道有人可能会使用格式化文件来指定 cmdlet 的默认输出属性(Writing a Windows PowerShell Formatting File) but I recently came across a more streamlined technique (i.e. just a couple lines of code; no separate formatting file needed) to do this in a scripted cmdlet (with thanks to Kirk Munro's Define default properties for custom objects,我从中复制了此代码示例):

$myObject = New-Object PSObject
$myObject | Add-Member NoteProperty Name ‘My Object’
$myObject | Add-Member NoteProperty Property1 1
$myObject | Add-Member NoteProperty Property2 2
$myObject | Add-Member NoteProperty Property3 3
$myObject | Add-Member NoteProperty Property4 4
$myObject | Add-Member NoteProperty Property5 5

$defaultProperties = @('Name','Property2','Property4')
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$myObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
$myObject

上面的最后一行显示了对象,只显示了指定的默认属性。 我一直在寻找一种等效的方法来在我的 C# cmdlet 中执行相同的步骤,但无济于事:我看不到 PSCmdlet 基础 class 上对 PSStandardMembers 的明显访问,我也没有找到任何讨论此事的帖子。这可能吗?如果可能,如何实现?


2015.03.26更新 感谢@Jaykul 指明了方向,为了完整起见,我附加了实际的 C# 代码来完成任务。我写这个方法是为了可重用,所以连同你希望 "filter" 的对象,你还传入了 属性 个感兴趣的名称。

PSObject SetDefaultProperties(object obj, IEnumerable<string> defaultProperties)
{
    var psObject = new PSObject(obj);
    psObject.Members.Add(
        new PSMemberSet("PSStandardMembers", new PSMemberInfo[]
        {
            new PSPropertySet("DefaultDisplayPropertySet", defaultProperties)
        }));
    return psObject;
}

以下是如何在 cmdlet 中使用它——而不是将完整的结果对象发送到 WriteObject,而是通过新方法将其汇集。

string[] DefaultProperties = {"Name", "Property2", "Property4" };
base.WriteObject(SetDefaultProperties(myObject, DefaultProperties));

您必须将对象包装在 PSObject 中,然后使用该包装器的方法来设置这些属性。

using System.Management.Automation;
public class Whatever {
   public string One { get { return "This is one"; } }
   public string Two { get { return "This is two"; } }
   public string Three { get { return "This is three"; } }
   public string Four { get { return "This is four"; } }

   public static PSObject Get() {

      var w = new Whatever();
      var pso = new PSObject(w);
      var display = new PSPropertySet("DefaultDisplayPropertySet",new []{"One","Two"});
      var mi = new PSMemberSet("PSStandardMembers", new[]{display});
      pso.Members.Add(mi);

      return pso;
   }
}

非常明确和明确:我正在回答你的问题,但我不推荐这种做法!即使在 PowerShell 脚本中,我也不喜欢这样做,尽管它有时是权宜之计,但我强烈建议不要在编译代码中这样做。

这样做可能会打破 ETS 系统的一些预期。这不是一个 "more streamlined technique" —— 它是一个围绕系统的 end-运行,我相信它会覆盖最终用户可能创建的任何 PSTypes 文件,从而巧妙地破坏他们控制格式的能力(这是PowerShell 的核心功能)。

当您创建一个简单的 PowerShell 脚本时,这可能是情有可原的(因为调整起来很容易),但是当您分发已编译的模块时,您应该只制作 PSTypes XML文件并注意格式化正确的、可扩展的方式,以便您的用户和他们的系统管理员可以修改它。

Michael,对于二进制模块,我同意 Joel 的观点,即为您的模块提供类型 and/or 格式的 ps1xml 文件以定义它在 PowerShell 中的呈现方式是更好的设计。但我真的不喜欢该信息(类型和格式数据)的管理与自定义 类 或您可能从命令返回的对象的定义相去甚远(这就是为什么,尤其是在 PowerShell 命令的情况下,像我之前 posted 那样的解决方案是有意义的——但是请注意,如果你同意 PowerShell 3.0 作为最低要求,你应该改用 Update-TypeData -DefaultDisplayPropertySet,因为这比我在 PowerShell 2.0 发布之前为该博客 post 创建的解决方法要容易得多)。必须管理 ps1xml 文件使得 type/format 信息的维护比它可能更繁重,如果通过在它所针对的类型旁边定义该配置的定义来简化这将是很好的定义。您可以使用我在 PowerShell 函数中的技术来做到这一点(虽然该技术在 PS 2.0 中被破坏了,但我确实在某个时候找到了一个我可以挖掘的解决方法),或者通过使用 Update-TypeData -DefaultDisplayPropertySet,但是对于在二进制模块内的 c# 类 中定义的类型,我还建议坚持创建 ps1xml 文件。通过使用通过模块清单 (psd1) 文件中的配置数据加载的 ps1xml 文件,您可以保证这些扩展将与您的模块一起加载,并且非常重要的是,当您的模块被卸载时它们也将被卸载。

重新审视这一点,现在你让我想到我应该创建一种机制,允许我在二进制模块本身中定义 type/format 信息,就在定义的类型旁边,也许使用属性或类似的东西,这样它就可以生成 type/format ps1xml 文件,您可以将这些文件作为包含二进制模块的包的一部分进行分发。嗯嗯……

应用方案更新:

关于 Jaykul 的解决方案(以及您 posted 的版本),它有效,但您可以(并且应该)也只需调用 Update-TypeData -DefaultDisplayPropertySet prop1,prop2,...。可以从您的二进制 cmdlet 代码轻松调用,我相信(但我自己没有测试过)可以使用 ps1xml 文件或其他 Update-TypeData 调用正确覆盖它。