从cmdlet

时间:2017-10-26 15:07:25

标签: c# powershell

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

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

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

public CommandA : PSCmdlet
{
  ...
}

public CommandB : PSCmdlet
{
  ...
}

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

1。选项

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

var cmd = new CommandB();
cmd.Invoke();

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

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

所以...下一个选项。

2。选项

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

var ps = PowerShell.Create();
ps.AddCommand("CommandB");
ps.Invoke();

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

我知道我可以重用运行空间。但是使用相同的运行空间 NOT 可以使我免于丢失重定向。如果CommandB会拨打Write-Verbose "Huzzah!",我就不会看到'Huzzah!'任何地方。

简而言之:我需要在与CommandB相同的PS实例中运行CommandA

第3。选项

使用ScriptBlock。像这样:

var sb = ScriptBlock.Create("CommandB");
sb.Invoke();

这很不错。但问题是,我无法将任何复杂的类参数传递给脚本块。如果CommandB的参数类型为......让我们说PSCredential,我就没有简单的方法将该参数传递给脚本。如果我有一个PowerShell对象,我可以很容易地做到

PowerShell ps
ps.AddCommand("CommandB");
ps.AddArgument("Credential", someCredentialObject);
ps.AddArgument("TargetUri", new Uri("www.google.de"));

但我不能用ScriptBlock。没错,我可以使用允许我将变量传递给scriptblock的InvokeWithContext,但我需要首先在变量中“包装”每个复杂的参数......相当麻烦。

结论

有什么想法吗?最好的办法是,如果我能以某种方式 - 从CommandA内部访问我正在运行的PowerShell的当前实例。我可以利用选项2而不会产生创建新实例的问题。但我不知道这是否可能......

2 个答案:

答案 0 :(得分:0)

我最终提出的解决方案是一个帮助器类,它实现了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;
    }

  }

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

  1. 您可以使用其静态方法InvokeCommand,并将cmdlet的名称和任何参数作为Hashtable传递。
  2. 创建PsInvoker类的实例并使用AddArgument添加参数,然后使用Invoke运行cmdlet。
  3. 再次感谢PetSerAl。

答案 1 :(得分:0)

如果您有PSCmdlet,则可以使用PSCmdlet.InvokeCommand.InvokeScript()来指定SessionState,是否使用新范围,还允许您将属性传递给它