Состояние текущего экземпляра PowerShell недопустимо для этой операции в C#

У меня есть метод ниже, который вызывается для разных сценариев PS, и я хотел бы, чтобы объект PowerShell был создан только один раз, и для этого я сделал объект Powershell как статический (см. Код ниже). но тогда это дает мне ошибку

Состояние текущего экземпляра PowerShell недопустимо для этой операции.

Как мне с этим справиться? как лучше всего оптимизировать мой код ниже?NB: приведенный ниже код отлично работает, если я удалю статику.

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

1 ответ

Пока можно создать экземпляров в коде C#, вы не можете напрямую выполнять их там - это возможно только черезэкземпляр (или из кода PowerShell). Нет причин создавать и взаимодействовать с ScriptBlock экземпляры прямо в вашем коде C#.

Вот пример того, как неявно создать блок скрипта с помощью <tcode id="2672794"></tcode>, и как передать ему аргумент :

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

}

Однако приведенное выше не может использоваться повторно , потому что после добавления аргументов или параметров с помощью или вы не можете удалить их и указать другие для выполнения другого вызова - насколько мне известно.

Обходной путь - использовать ввод конвейера PowerShell (как предоставляется через необязательный input параметр, который вы можете передать <tcode id="2672799"></tcode>, так как это позволяет повторять вызовы с разными входными данными. Однако ваш блок сценария должен быть построен соответствующим образом:

      // 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 - подчиняются эффективной <em>политике выполнения</em> , которая может помешать их выполнению.

Без блоков скрипта вам пришлось бы вызывать одну или несколько команд по отдельности, используя <tcode id="2672801"></tcode> вызовы, разделяя несколько независимых команд с помощью <tcode id="2672802"></tcode>.

  • Если одна команда или конвейер команд принимает весь ввод через конвейер , вы можете использовать тот же подход, что и выше.

  • В противном случае - если .AddParameter(s) / .AddArgument() нужны - вам нужно будет позвонить ps.Commands.Clear()и повторно добавляйте команды перед каждым (повторным) вызовом ; однако по сравнению с вызовом .AddScript(), это должно привести к небольшим накладным расходам.


Адаптация многоразовой техники к вашему коду :

Учебный класс DataRulesPSScripts, который использует статический PowerShell instance и один раз добавляет блок скрипта в свой статический конструктор.

      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() {
    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 });
  }

}

Код, использующий класс, который передает параметры через конвейер :

      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):

      Console.WriteLine(
  obj.GenerateRowKey(
    new Dictionary<string, EntityProperty> { ["ParamA"] = new EntityProperty { Value = "bar" },  ["ParamB"] = new EntityProperty { Value = "baz" } }
  ).Result
);

Вышеупомянутое должно дать что-то вроде:

      @{Row=demo.EntityProperty; Key=2}

Это строковое представление второго настраиваемого объекта, выводимого блоком сценария.

Другие вопросы по тегам