c# - 从基于 C# 的 PSCmdLet 调用 CmdLet,提供输入和捕获输出
问题描述
我正在构建一个用于出色打印的 Powershell CmdLet。它的功能应该与winprintOut-Print
的所有花里胡哨的功能一样。
PS> get-help out-winprint
NAME
Out-WinPrint
SYNTAX
Out-WinPrint [[-Name] <string>] [-SheetDefintion <string>] [-ContentTypeEngine <string>]
[-InputObject <psobject>] [<CommonParameters>]
ALIASES
wp
为此,我需要获取InputObject
我的实现的输入流 ( )PSCmdLet
并将其传递,Out-String
因此它全部被扩展和格式化。我在想最好的方法是使用CommandInvocationIntrinsics.InvokeScript
to invoke out-string
,它应该给我一个字符串的输出......
protected override void ProcessRecord() {
if (InputObject == null || InputObject == AutomationNull.Value) {
return;
}
IDictionary dictionary = InputObject.BaseObject as IDictionary;
if (dictionary != null) {
// Dictionaries should be enumerated through because the pipeline does not enumerate through them.
foreach (DictionaryEntry entry in dictionary) {
ProcessObject(PSObject.AsPSObject(entry));
}
}
else {
ProcessObject(InputObject);
}
}
private void ProcessObject(PSObject input) {
object baseObject = input.BaseObject;
// Throw a terminating error for types that are not supported.
if (baseObject is ScriptBlock ||
baseObject is SwitchParameter ||
baseObject is PSReference ||
baseObject is PSObject) {
ErrorRecord error = new ErrorRecord(
new FormatException("Invalid data type for Out-WinPrint"),
DataNotQualifiedForWinprint,
ErrorCategory.InvalidType,
null);
this.ThrowTerminatingError(error);
}
_psObjects.Add(input);
}
protected override async void EndProcessing() {
base.EndProcessing();
//Return if no objects
if (_psObjects.Count == 0) {
return;
}
var text = this.SessionState.InvokeCommand.InvokeScript(@"Out-String", true, PipelineResultTypes.None, _psObjects, null);
// Just for testing...
this.WriteObject(text, false);
...
假设我像这样调用了我的 cmdlet:
PS> get-help out-winprint -full | out-winprint`
如果我理解这应该如何工作,那么text
上面的 var 应该是 astring
并且WriteObject
call 应该显示out-string
将显示的内容(即 的结果get-help out-winprint -full
)。
然而,实际上text
是string[] = { "" }
(具有一个元素的字符串数组,一个空字符串)。
我究竟做错了什么?
解决方案
你做错了两件非常小的事情:
从字面上看,该方法被称为InvokeScript
您传递的是一个scriptblock
.
现在,你ScriptBlock
的基本上是这样的:
$args = @(<random stuff>) # this line is implicit of course,
# and $args will have the value of whatever your _psObjects has
Out-String
因此,您可以告诉脚本中的参数,您只是没有使用它们。所以你想要一些更像这样的东西而不是你的脚本:
Out-String -InputObject $args
只是现在的问题是它Out-String
实际上并不喜欢被给予 a Object[]
,-InputObject
所以你的脚本必须是这样的:
$args | Out-String
或者一些变体,比如使用 a foreach
,你就明白了。
您的第二个错误是您传递_psObjects
了错误的参数 - 它应该是:
this.SessionState.InvokeCommand.InvokeScript(<ScriptBlock>, true, PipelineResultTypes.None, null, _psObjects);
官方文档在这方面真的很糟糕,我完全不知道另一个参数是做什么用的。
其中一个重载是列表:
input
=命令的可选输入
args
= 传递给脚本块的参数
但是在下一次重载时,它会说以下内容:
input
= 用作脚本输入的对象列表。
args
=命令的参数数组。
我只能告诉你的是,在我的测试中,当我按照说明进行操作时它会起作用。希望有帮助!
供参考,经过测试和工作的 PS 代码:
function Test
{
[CmdletBinding()]
param()
$results = $PSCmdlet.SessionState.InvokeCommand.InvokeScript('$args | Out-String', $false, "None", $null, "Hello World!")
foreach ($item in $results)
{
$item
}
}
Test
编辑
根据我的测试,我应该补充一点,如果你向两者都传递了一些东西input
,args
那么$args
脚本中的内容就会是空的。就像我说的,根本不知道做什么input
,只是传递null
给它。
编辑 2
正如 tig 所提到的,在PowerShell 问题 12137上,传递给的任何内容都input
将绑定到脚本块$input
内的变量,这意味着要么使用input
要么args
可以使用。
话虽这么说...小心使用$input
- 它是一个集合,包含的内容超过通过input
参数传递的内容:根据我的测试,索引 0 将包含 a bool
,即在第二个参数上传递的内容InvokeScript()
,索引 1 将包含aPipelineResultTypes
那是InvokeScript()
在第三个参数上传递给的任何内容。
此外,我不建议PowerShell.Create()
在这种情况下使用:为什么要创建一个新的 PowerShell 实例,PSCmdlet
而这意味着你已经有了一个?
我仍然认为使用args
/$args
是最好的解决方案。当然,您也可以通过使用类似以下的 ScriptBlock 使事情变得更好(尽管在这种情况下完全不需要):
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[PSObject[]]
$objects
)
Out-String -InputObject $objects
这也会更快,因为您不再依赖(慢)管道。
只是不要忘记,现在你需要把你_psObjects
的object[]
喜欢包裹起来:
this.SessionState.InvokeCommand.InvokeScript(<ScriptBlock>, true, PipelineResultTypes.None, null, new object[] {_psObjects});