当前 PowerShell 实例的状态对于 C# 中的此操作无效 [英] The state of the current PowerShell instance is not valid for this operation in C#

查看:90
本文介绍了当前 PowerShell 实例的状态对于 C# 中的此操作无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有下面的方法,它被不同的 PS 脚本调用,我希望 PowerShell 对象只创建一次,为此我将 Powershell 对象设为静态(见下面的代码).但随后它给了我错误

I am having below method which is being called for different PS scripts and I would like the PowerShell object to be created only once and for that I made the Powershell object as static(see below code). but then it gives me error

当前 PowerShell 实例的状态对此无效操作.

The state of the current PowerShell instance is not valid for this operation.

我该如何处理?优化我下面的代码的最佳方法是什么?注意:如果我删除静态,下面的代码可以正常工作.

How should I handle this? what is the best way to optimize my below code? NB: Below code works fine if I remove static.

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 代码作为字符串传递给PowerShell.AddScript() 方法,并通过 PowerShell 实例的调用,.Invoke() 方法.

There is no reason to create and interact with ScriptBlock instances directly in your C# code - they are used internally by the PowerShell SDK:[1] They are internally created and stored when you pass a piece of PowerShell code as a string to the PowerShell.AddScript() method, and are invoked via the PowerShell instance's, .Invoke() method.

虽然您的间接获取脚本块以在 C# 代码中直接执行的方式是通过让 PowerShell实例通过 .AddScript() 调用为您创建和输出它 (ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;) 确实为您提供了一个脚本块,您可以通过其 .Invoke() 方法直接从 C# 代码调用该脚本块(如果您直接在C# 代码,由于未连接到 PowerShell runspace),您将无法全部调用它,此类调用仅提供 成功 输出 - 所有其他的输出PowerShell 流丢失 - 即原始 PowerShell 实例的 .Streams 正确ty 不会反映这样的输出,这尤其会使发生的非终止错误不可访问,同样,.HadErrors 属性不会反映是否发生了非终止错误.因此,这种方法应该避免.[2]

While your indirect way of obtaining a script block for direct execution in C# code by letting a PowerShell instance create and output it for you via an .AddScript() call (ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;) does give you a script block that you can call directly from C# code via its .Invoke() method (if you created the script block directly in C# code, you wouldn't be able to invoke it all, due to not being connected to a PowerShell runspace), such calls only provide success output - output from all other PowerShell streams would be lost - that is, the originating PowerShell instance's .Streams property wouldn't reflect such output, which notably makes non-terminating errors that occurred inaccessible, and, similarly, the .HadErrors property would not reflect whether non-terminating errors occurred. Therefore, this approach should be avoided.[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 的参数(),你不能删除它们并指定不同的来执行另一个调用 - 据我所知.

However, the above isn't reusable, because once you've added arguments or parameters with .AddParameter(s) or .AddArgument(), you cannot remove them and specify different ones to perform another call - as far as I know.

解决方法是使用 PowerShell 管道输入(通过可选的 input 参数提供,您可以传递给 PowerShell.Invoke(),因为这允许使用不同的输入重复调用.但是,您的脚本块必须相应地构建:

The workaround is to use PowerShell pipeline input (as provided via the optional input parameter you can pass to PowerShell.Invoke(), as that enables repeated invocations with different input. However, your script block must then be constructed accordingly:

// 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);
    }

  }

}


或者,如果可行,考虑制作没有脚本块,因为它们需要解析(尽管在这种情况下是一次性开销)并且 - 在 Windows 上 -受有效执行政策,这可能会阻止它们的执行.


Alternatively, if feasible, consider making do without script blocks, as they require parsing (albeit as one-time overhead in this case) and - on Windows - are subject to the effective execution policy, which could prevent their execution.

如果没有脚本块,您必须使用 PowerShell.AddCommand() 调用,使用 PowerShell.AddStatement().

Without script blocks, you'd have to invoke one or more commands individually, using PowerShell.AddCommand() calls, separating multiple independent commands with PowerShell.AddStatement().

  • 如果单个命令或命令管道接受所有输入通过管道,您可以使用与上述相同的方法.

  • If a single command or a pipeline of command accepts all input via the pipeline, you can use the same approach as above.

否则 - 如果需要 .AddParameter(s)/.AddArgument() - 你必须调用 ps.Commands.Clear()在每次(重复)调用之前重新添加命令;然而,与调用 .AddScript() 相比,这应该引入很少的开销.

Otherwise - if .AddParameter(s) / .AddArgument() are needed - you'd have to call ps.Commands.Clear() and re-add the commands before every (repeat) invocation; however, compared to calling .AddScript(), this should introduce little overhead.

使可重用技术适应您的代码:

Class DataRulesPSScripts,使用静态 PowerShell 实例并在其静态构造函数中添加一次脚本块.

Class DataRulesPSScripts, which uses a static PowerShell instance and adds the script block once, in its static constructor.

  • 注意:考虑使用 instance 属性,并使类实现 IDisposable 以允许类的用户控制 PowerShell 实例的生命周期.
  • Note: Consider using an instance property instead and making the class implement IDisposable to allow users of the class control over the PowerShell instance's lifecycle.
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 });
  }

}

使用类的代码,通过管道传递参数:

The code that uses the class, which passes the parameters via the pipeline:

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 类具有属性 .Value):

Sample call (obj is the object that contains the methods above; assumes a simplified EntityProperty class with property .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}

这是脚本块输出的第二个自定义对象的字符串表示.

This is the string representation of the 2nd custom object output by the script block.

[1]在PowerShell脚本代码中,相比之下,ScriptBlock实例被直接使用,通常以script-block 文字 ({ ... }),使用 & 调用,调用运算符.

[1] In PowerShell script code, by contrast, ScriptBlock instances are used directly, typically in the form of script-block literals ({ ... }), invoked with &, the call operator.

[2] 这是 PowerShell 的快速演示:
$ps=[PowerShell]::Create();$sb = $ps.AddScript("{ 'hi'; Get-Item nosuchfile }").Invoke()[0];调用结果:$($sb.Invoke())";有错误:$($ps.HadErrors)";错误流:$($ps.Streams.Error)"
即使调用产生了非终止性错误,.HadErrors 仍报告 $false,并且 .Streams.Error 为空.

[2] Here's a quick demonstration from 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)"
Even though the call produced a non-terminating error, .HadErrors reports $false, and the .Streams.Error is empty.

这篇关于当前 PowerShell 实例的状态对于 C# 中的此操作无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆