当前 PowerShell 实例的状态对于 C# 中的此操作无效
The state of the current PowerShell instance is not valid for this operation in C#
我正在为不同的 PS 脚本调用以下方法,我希望只创建一次 PowerShell 对象,为此我将 Powershell 对象设置为静态对象(参见下面的代码)。但它给了我错误
The state of the current PowerShell instance is not valid for this
operation.
我该如何处理?优化我的以下代码的最佳方法是什么?
注意:如果我删除静态,下面的代码可以正常工作。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
public IEnumerable<object> RunScriptBlock( ScriptBlock scriptBlock, Dictionary<string, object> scriptParameters )
{
var vars = scriptParameters.Select( p => new PSVariable( p.Key, p.Value ) ).ToList();
return scriptBlock.InvokeWithContext( null, vars );
}
public async Task<ScriptBlock> CreateScriptBlock( string pSScript )
{
ps.AddScript( pSScript );
var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
return scriptBlock;
}
}
}
这是从这里调用的:
internal async Task<string> GeneratePartitionKey( Dictionary<string, EntityProperty> arg)
{
var result =await GenerateKeys(arg);
return result[0].ToString();
}
internal async Task<string> GenerateRowKey( Dictionary<string, EntityProperty> arg )
{
var result = await GenerateKeys( arg );
return result[1].ToString();
}
private async Task<List<object>> GenerateKeys( Dictionary<string, EntityProperty> arg )
{
var pars = new Dictionary<string, object>();
pars.Add( "_", arg );
DataRulesPSScripts ds = new DataRulesPSScripts();
var scriptBlock = await ds.CreateScriptBlock( PSScript );
var results = ds.RunScriptBlock( scriptBlock, pars ).ToList();
return results;
}
没有理由直接在您的 C# 代码中创建 ScriptBlock
实例并与其交互 - PowerShell SDK 内部 使用它们:[1] 当您将一段 PowerShell 代码作为 string 传递给 PowerShell.AddScript()
method, and are invoked via the PowerShell
instance's, .Invoke()
方法。
虽然你的间接获取脚本块的方式在C#代码中直接执行让 PowerShell
实例通过 .AddScript()
调用 (ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
) 为您创建和输出它 does 给您一个脚本块,您可以调用直接从 C# 代码通过它的 .Invoke()
方法(如果你直接在 C# 代码中创建脚本块,你将无法调用它,因为没有连接到 PowerShell runspace), 此类调用仅提供 成功 输出 - 所有其他 PowerShell streams 的输出将是 lost - 也就是说,原始 PowerShell
实例的 .Streams
属性 不会反映此类输出,这尤其使非终止发生的错误无法访问,同样,.HadErrors
属性也不会反映是否发生了非终止错误。所以这种做法应该避免.[2]
这是一个示例,通过 PowerShell.AddScript()
、 在幕后 隐式地创建脚本块 ]向它传递一个参数并调用它:
// Define the script-block text.
// It expects a single argument that is an object with .Name and .Code
// properties, whose values are echoed.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $args[0]; $obj.Name; $obj.Code";
// Define an object to pass to the script block.
var obj = new { Name = "Abc", Code = 42 };
using (var ps = PowerShell.Create()) {
// Add the script block and an argument to pass to it.
ps
.AddScript(scriptBlockText)
.AddArgument(obj);
// Invoke and echo the results.
foreach (var o in ps.Invoke()) {
Console.WriteLine(o);
}
}
但是,以上内容不可重复使用,因为一旦您使用 .AddParameter(s)
或 .AddArgument()
添加参数或参数,你不能删除它们并指定不同的 来执行另一个调用 - 据我所知。
- GitHub issue #15004 包含相关讨论。
解决方法是使用PowerShell 管道输入(通过可选的input
参数提供,您可以传递给PowerShell.Invoke()
,因为 允许使用不同的输入重复调用。
但是,您的脚本块必须相应地构建:
// Define the script-block text.
// This time, expect the input to come via the *pipeline*, which
// can be accessed via the $input enumerator.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $($input); $obj.Name; $obj.Code";
// Define two objects to pass to the script block, one each in
// two separate invocations:
object[] objects = {
new { Name = "Abc", Code = 42 },
new { Name = "Def", Code = 43 }
};
using (var ps = PowerShell.Create()) {
// Add the script block.
ps.AddScript(scriptBlockText);
// Perform two separate invocations.
foreach (var obj in objects) {
// For housekeeping, clean the previous non-success streams.
ps.Streams.ClearStreams();
// Invoke the script block with the object at hand and echo the results.
// Note: The input argument must be an enumerable, so we wrap the object
// in an aux. array.
foreach (var o in ps.Invoke(new[] { obj })) {
Console.WriteLine(o);
}
}
}
或者,如果可行,考虑在没有 脚本块的情况下,因为它们需要解析(尽管这是一次性开销case) 和 - 在 Windows - 受有效 execution policy, which could prevent their execution (though you can bypass such a restriction on a per-process basis, see this answer).
约束
如果没有脚本块,您将不得不使用 PowerShell.AddCommand()
calls, separating multiple independent commands with PowerShell.AddStatement()
.
单独调用一个或多个命令
如果单个命令或命令管道通过管道接受所有输入,您可以使用与上述相同的方法。
否则 - 如果需要 .AddParameter(s)
/ .AddArgument()
- 你必须先调用 ps.Commands.Clear()
和 重新添加命令每次(重复)调用;但是,与调用 .AddScript()
相比,这应该会引入很少的开销。
使可重用技术适应您的代码:
ClassDataRulesPSScripts
,它使用静态PowerShell
实例
并在其静态构造函数中添加一次脚本块。
- 注意:考虑使用 实例 属性 并使 class 实现
IDisposable
以允许 [=184= 的用户] 控制 PowerShell 实例的生命周期。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
// The script-block text:
// Note that $ParamA and $ParamB must correspond to the keys of the
// dictionary passed to the script block on invocation via .InvokeAsync()
static string PSScript = @"$argDict = $($input); & { param($ParamA, $ParamB) [pscustomobject] @{ Partition = $ParamA; Key = 1 }, [pscustomobject] @{ Row = $ParamB; Key = 2 } } @argDict";
static DataRulesPSScripts() {
// Add the script-block text only once, which therefore incurs the
// overhead of parsing the text into a script block only once,
// and allows repeated later invocations via .Invoke() with pipeline input.
ps.AddScript(PSScript);
}
public async Task<IEnumerable<object>> RunScriptBlock(Dictionary<string, EntityProperty> scriptParameters)
{
// Pass the parameter dictionary as pipeline input.
// Note: Since dictionaries are enumerable themselves, an aux. array
// is needed to pass the dictionary as a single object.
return await ps.InvokeAsync<object>(new [] { scriptParameters });
}
}
使用class的代码,通过管道传递参数:
internal async Task<string> GeneratePartitionKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[0].ToString();
}
internal async Task<string> GenerateRowKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[1].ToString();
}
private async Task<List<object>> GenerateKeys(Dictionary<string, EntityProperty> args)
{
DataRulesPSScripts ds = new DataRulesPSScripts();
var results = await ds.RunScriptBlock(args);
return results.ToList();
}
示例调用(obj
是包含上述方法的对象;假设一个简化的 EntityProperty
class 和 属性 .Value
):
Console.WriteLine(
obj.GenerateRowKey(
new Dictionary<string, EntityProperty> { ["ParamA"] = new EntityProperty { Value = "bar" }, ["ParamB"] = new EntityProperty { Value = "baz" } }
).Result
);
上面的结果应该是这样的:
@{Row=demo.EntityProperty; Key=2}
这是脚本块输出的第二个自定义对象的字符串表示形式。
[1]在PowerShell脚本代码中,相比之下,ScriptBlock
个实例是直接使用的,通常采用 script-block literals ({ ... }
), invoked with &
, the call operator.
的形式
[2] 下面是 PowerShell 的快速演示:
$ps=[PowerShell]::Create(); $sb = $ps.AddScript("{ 'hi'; Get-Item nosuchfile }").Invoke()[0]; "call result: $($sb.Invoke())"; "had errors: $($ps.HadErrors)"; "error stream: $($ps.Streams.Error)"
即使调用产生了非终止错误,.HadErrors
报告 $false
,并且 .Streams.Error
为空。
我正在为不同的 PS 脚本调用以下方法,我希望只创建一次 PowerShell 对象,为此我将 Powershell 对象设置为静态对象(参见下面的代码)。但它给了我错误
The state of the current PowerShell instance is not valid for this operation.
我该如何处理?优化我的以下代码的最佳方法是什么? 注意:如果我删除静态,下面的代码可以正常工作。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
public IEnumerable<object> RunScriptBlock( ScriptBlock scriptBlock, Dictionary<string, object> scriptParameters )
{
var vars = scriptParameters.Select( p => new PSVariable( p.Key, p.Value ) ).ToList();
return scriptBlock.InvokeWithContext( null, vars );
}
public async Task<ScriptBlock> CreateScriptBlock( string pSScript )
{
ps.AddScript( pSScript );
var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
return scriptBlock;
}
}
}
这是从这里调用的:
internal async Task<string> GeneratePartitionKey( Dictionary<string, EntityProperty> arg)
{
var result =await GenerateKeys(arg);
return result[0].ToString();
}
internal async Task<string> GenerateRowKey( Dictionary<string, EntityProperty> arg )
{
var result = await GenerateKeys( arg );
return result[1].ToString();
}
private async Task<List<object>> GenerateKeys( Dictionary<string, EntityProperty> arg )
{
var pars = new Dictionary<string, object>();
pars.Add( "_", arg );
DataRulesPSScripts ds = new DataRulesPSScripts();
var scriptBlock = await ds.CreateScriptBlock( PSScript );
var results = ds.RunScriptBlock( scriptBlock, pars ).ToList();
return results;
}
没有理由直接在您的 C# 代码中创建 ScriptBlock
实例并与其交互 - PowerShell SDK 内部 使用它们:[1] 当您将一段 PowerShell 代码作为 string 传递给 PowerShell.AddScript()
method, and are invoked via the PowerShell
instance's, .Invoke()
方法。
虽然你的间接获取脚本块的方式在C#代码中直接执行让 PowerShell
实例通过 .AddScript()
调用 (ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
) 为您创建和输出它 does 给您一个脚本块,您可以调用直接从 C# 代码通过它的 .Invoke()
方法(如果你直接在 C# 代码中创建脚本块,你将无法调用它,因为没有连接到 PowerShell runspace), 此类调用仅提供 成功 输出 - 所有其他 PowerShell streams 的输出将是 lost - 也就是说,原始 PowerShell
实例的 .Streams
属性 不会反映此类输出,这尤其使非终止发生的错误无法访问,同样,.HadErrors
属性也不会反映是否发生了非终止错误。所以这种做法应该避免.[2]
这是一个示例,通过 PowerShell.AddScript()
、 在幕后 隐式地创建脚本块 ]向它传递一个参数并调用它:
// Define the script-block text.
// It expects a single argument that is an object with .Name and .Code
// properties, whose values are echoed.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $args[0]; $obj.Name; $obj.Code";
// Define an object to pass to the script block.
var obj = new { Name = "Abc", Code = 42 };
using (var ps = PowerShell.Create()) {
// Add the script block and an argument to pass to it.
ps
.AddScript(scriptBlockText)
.AddArgument(obj);
// Invoke and echo the results.
foreach (var o in ps.Invoke()) {
Console.WriteLine(o);
}
}
但是,以上内容不可重复使用,因为一旦您使用 .AddParameter(s)
或 .AddArgument()
添加参数或参数,你不能删除它们并指定不同的 来执行另一个调用 - 据我所知。
- GitHub issue #15004 包含相关讨论。
解决方法是使用PowerShell 管道输入(通过可选的input
参数提供,您可以传递给PowerShell.Invoke()
,因为 允许使用不同的输入重复调用。
但是,您的脚本块必须相应地构建:
// Define the script-block text.
// This time, expect the input to come via the *pipeline*, which
// can be accessed via the $input enumerator.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $($input); $obj.Name; $obj.Code";
// Define two objects to pass to the script block, one each in
// two separate invocations:
object[] objects = {
new { Name = "Abc", Code = 42 },
new { Name = "Def", Code = 43 }
};
using (var ps = PowerShell.Create()) {
// Add the script block.
ps.AddScript(scriptBlockText);
// Perform two separate invocations.
foreach (var obj in objects) {
// For housekeeping, clean the previous non-success streams.
ps.Streams.ClearStreams();
// Invoke the script block with the object at hand and echo the results.
// Note: The input argument must be an enumerable, so we wrap the object
// in an aux. array.
foreach (var o in ps.Invoke(new[] { obj })) {
Console.WriteLine(o);
}
}
}
或者,如果可行,考虑在没有 脚本块的情况下,因为它们需要解析(尽管这是一次性开销case) 和 - 在 Windows - 受有效 execution policy, which could prevent their execution (though you can bypass such a restriction on a per-process basis, see this answer).
约束如果没有脚本块,您将不得不使用 PowerShell.AddCommand()
calls, separating multiple independent commands with PowerShell.AddStatement()
.
如果单个命令或命令管道通过管道接受所有输入,您可以使用与上述相同的方法。
否则 - 如果需要
.AddParameter(s)
/.AddArgument()
- 你必须先调用ps.Commands.Clear()
和 重新添加命令每次(重复)调用;但是,与调用.AddScript()
相比,这应该会引入很少的开销。
使可重用技术适应您的代码:
ClassDataRulesPSScripts
,它使用静态PowerShell
实例
并在其静态构造函数中添加一次脚本块。
- 注意:考虑使用 实例 属性 并使 class 实现
IDisposable
以允许 [=184= 的用户] 控制 PowerShell 实例的生命周期。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
// The script-block text:
// Note that $ParamA and $ParamB must correspond to the keys of the
// dictionary passed to the script block on invocation via .InvokeAsync()
static string PSScript = @"$argDict = $($input); & { param($ParamA, $ParamB) [pscustomobject] @{ Partition = $ParamA; Key = 1 }, [pscustomobject] @{ Row = $ParamB; Key = 2 } } @argDict";
static DataRulesPSScripts() {
// Add the script-block text only once, which therefore incurs the
// overhead of parsing the text into a script block only once,
// and allows repeated later invocations via .Invoke() with pipeline input.
ps.AddScript(PSScript);
}
public async Task<IEnumerable<object>> RunScriptBlock(Dictionary<string, EntityProperty> scriptParameters)
{
// Pass the parameter dictionary as pipeline input.
// Note: Since dictionaries are enumerable themselves, an aux. array
// is needed to pass the dictionary as a single object.
return await ps.InvokeAsync<object>(new [] { scriptParameters });
}
}
使用class的代码,通过管道传递参数:
internal async Task<string> GeneratePartitionKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[0].ToString();
}
internal async Task<string> GenerateRowKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[1].ToString();
}
private async Task<List<object>> GenerateKeys(Dictionary<string, EntityProperty> args)
{
DataRulesPSScripts ds = new DataRulesPSScripts();
var results = await ds.RunScriptBlock(args);
return results.ToList();
}
示例调用(obj
是包含上述方法的对象;假设一个简化的 EntityProperty
class 和 属性 .Value
):
Console.WriteLine(
obj.GenerateRowKey(
new Dictionary<string, EntityProperty> { ["ParamA"] = new EntityProperty { Value = "bar" }, ["ParamB"] = new EntityProperty { Value = "baz" } }
).Result
);
上面的结果应该是这样的:
@{Row=demo.EntityProperty; Key=2}
这是脚本块输出的第二个自定义对象的字符串表示形式。
[1]在PowerShell脚本代码中,相比之下,ScriptBlock
个实例是直接使用的,通常采用 script-block literals ({ ... }
), invoked with &
, the call operator.
[2] 下面是 PowerShell 的快速演示:
$ps=[PowerShell]::Create(); $sb = $ps.AddScript("{ 'hi'; Get-Item nosuchfile }").Invoke()[0]; "call result: $($sb.Invoke())"; "had errors: $($ps.HadErrors)"; "error stream: $($ps.Streams.Error)"
即使调用产生了非终止错误,.HadErrors
报告 $false
,并且 .Streams.Error
为空。