我有一个完全用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而不会产生创建新实例的问题。但我不知道这是否可能......
答案 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的方法:
InvokeCommand
,并将cmdlet的名称和任何参数作为Hashtable
传递。AddArgument
添加参数,然后使用Invoke
运行cmdlet。再次感谢PetSerAl。
答案 1 :(得分:0)
如果您有PSCmdlet
,则可以使用PSCmdlet.InvokeCommand.InvokeScript()
来指定SessionState
,是否使用新范围,还允许您将属性传递给它