Вызов CmdLet из PSCmdLet на основе C#, обеспечивающий ввод и захват вывода - PullRequest
1 голос
/ 16 марта 2020

Я создаю Powershell CmdLet для потрясающей печати. Он должен функционировать так же, как Out-Print, но со всеми наворотами winprint .

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 для вызова 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);
     ...

Предположим, я вызвал свой командлет следующим образом :

PS> get-help out-winprint -full | out-winprint`

Если я понимаю, как это должно работать, приведенная выше переменная text должна быть string, а вызов WriteObject должен отображать то, что будет отображаться out-string (а именно результат get-help out-winprint -full).

Однако в действительности text - это string[] = { "" } (массив строк с одним элементом, пустой строкой).

Что я делаю не так?

1 Ответ

1 голос
/ 17 марта 2020

Вы делаете две очень маленькие вещи неправильно:

Метод называется 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 на самом деле не нравится, когда ему присваивают Object[] как -InputObject, поэтому вместо этого ваш сценарий должен выглядеть примерно так:

$args | Out-String

Или что-то вроде этого, например использование foreach, вы поймете идею.

Ваша вторая ошибка в том, что вы передаете _psObjects на неправильный параметр - это должно быть:

this.SessionState.InvokeCommand.InvokeScript(<ScriptBlock>, true, PipelineResultTypes.None, null, _psObjects);

Официальная документация очень плохая, и я абсолютно не знаю, для чего нужен другой параметр.

В одной из перегрузок есть списки:

input = необязательный ввод для команды

args = Аргументы для передачи в scriptblock

Но при следующей перегрузке он говорит следующее:

input = Список объектов для использования в качестве входных данных для script .

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 будет содержать bool, то есть то, что передано на 2-й параметр InvokeScript() и индекс 1 будут содержать PipelineResultTypes, то есть все, что было передано InvokeScript() по 3-му параметру.

Также я бы не рекомендовал использовать 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});
...