从 cmdlet 内调用 PSCmdlet

Hem*_*era 5 c# powershell

我有一个完全用 C# 编写的 PS 模块。它包含大约 20 个已投入生产的 Cmdlet。其中一些“共享代码”。举个例子:

我有一个名为的 Cmdlet InvokeCommitCommand,可以生成“变更集”。此 Cmdlet 还发布此变更集的元数据。我现在想创建一个名为 的新 Cmdlet PublishCommitCommand,可以独立调用它来执行现有变更集的“发布”。因此,我想重构InvokeCommitCommand以利用新的 CmdletPublishCommitCommand并避免代码重复。

更一般地说...我试图CommandB从 cmdlet调用 cmdlet CommandA。它们的定义如下

public CommandA : PSCmdlet
{
  ...
}

public CommandB : PSCmdlet
{
  ...
}
Run Code Online (Sandbox Code Playgroud)

我这里有几个选择。但它们都不起作用。

1. 选项

CommandB通过创建它的实例来调用。这是我的第一个猜测。就像这样:

var cmd = new CommandB();
cmd.Invoke();
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不起作用。我得到例外:

无法直接调用从 PSCmdlet 派生的 Cmdlet ...

所以...下一个选择。

2. 选项

创建 PowerShell 实例并运行命令。就像这样:

var ps = PowerShell.Create();
ps.AddCommand("CommandB");
ps.Invoke();
Run Code Online (Sandbox Code Playgroud)

不幸的是这也不起作用。这会导致创建一个新的 PowerShell 实例,因此我会丢失可能已附加到当前正在运行的 PowerShell 实例的所有流重定向。

我知道我可以重用运行空间。但使用相同的运行空间并不能避免我丢失重定向。如果CommandB我打电话Write-Verbose "Huzzah!",我就不会看到“哈扎!” 任何地方。

简而言之:我需要CommandB在同一个 PS 实例中运行CommandA

3. 选项

使用脚本块。就像这样:

var sb = ScriptBlock.Create("CommandB");
sb.Invoke();
Run Code Online (Sandbox Code Playgroud)

那真是太好了。但这里的问题是,我无法将任何复杂的类参数传递给脚本块。如果CommandB有一个 ... 类型的参数PSCredential,那么我没有简单的方法将该参数传递给脚本。如果我有一个PowerShell对象,我可以轻松做到

PowerShell ps
ps.AddCommand("CommandB");
ps.AddArgument("Credential", someCredentialObject);
ps.AddArgument("TargetUri", new Uri("www.google.de"));
Run Code Online (Sandbox Code Playgroud)

但我不能用一个ScriptBlock。确实,我可以使用InvokeWithContext它允许我将变量传递给脚本块,但我需要先将每个复杂的参数“包装”在变量中......相当麻烦。

结论

有任何想法吗?最好的事情是,如果我能以某种方式从内部CommandA访问PowerShell我正在运行的当前实例。然后我可以利用选项 2,而不会出现创建新实例的问题。但我不知道这是否可能......

Hem*_*era 2

我最终提出的解决方案是一个实现PetSerAl建议的方法的辅助类。我使用像上面第三个选项一样的 ScriptBlock,但进行了一些更改以使传递参数不那么繁琐。

所以这是我的助手类,它很好地完成了这项工作:

  public class PsInvoker
  {

    public static PSObject[] InvokeCommand(string commandName, Hashtable parameters)
    {
      var sb = ScriptBlock.Create("param($Command, $Params) & $Command @Params");
      return sb.Invoke(commandName, parameters).ToArray();
    }

    public static PSObject[] InvokeCommand<T>(Hashtable parameters) where T : Cmdlet
    {
      return InvokeCommand(Extensions.GetCmdletName<T>(), parameters);
    }

    public static PsInvoker Create(string cmdletName)
    {
      return new PsInvoker(cmdletName);
    }

    public static PsInvoker Create<T>() where T : Cmdlet
    {
      return new PsInvoker(Extensions.GetCmdletName<T>());
    }


    private Hashtable Parameters { get; set; }


    public string CmdletName { get; }

    public bool Invoked { get; private set; }

    public PSObject[] Result { get; private set; }


    private PsInvoker(string cmdletName)
    {
      CmdletName = cmdletName;
      Parameters = new Hashtable();
    }


    public void AddArgument(string name, object value)
    {
      Parameters.Add(name, value);
    }

    public void AddArgument(string name)
    {
      Parameters.Add(name, null);
    }

    public PSObject[] Invoke()
    {
      if (Invoked)
        throw new InvalidOperationException("This instance has already been invoked.");
      var sb = ScriptBlock.Create("param($Command, $Params) & $Command @Params");
      Result = sb.Invoke(CmdletName, Parameters).ToArray();
      Invoked = true;
      return Result;
    }

  }
Run Code Online (Sandbox Code Playgroud)

该类基本上提供了两种调用 cmdlet 的方法:

  1. 您可以使用其静态方法InvokeCommand并将 cmdlet 的名称以及任何参数作为Hashtable.
  2. 创建 PsInvoker 类的实例并用于AddArgument添加参数,然后用于Invoke运行 cmdlet。

再次感谢 PetSerAl。