Как передать "линию аргументов" одной функции PowerShell другому?

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

Я устал использовать "splat", чтобы сделать это с помощью @args, но это не сработало, как я ожидал.

В приведенном ниже примере я написал игрушечную функцию под названием myls, которая должна печатать привет! а затем вызовите ту же встроенную функцию Get-ChildItem, что встроенный псевдоним ls вызывает с остальной частью строки аргумента неповрежденной. То, что я до сих пор работает очень хорошо:

function myls
{
  Write-Output "hello!"
# $MyInvocation | Format-List          # <-- uncomment this line for debug info
  Invoke-Expression ("Get-ChildItem " + $MyInvocation.UnboundArguments -join " ")
}

Правильная версия myls должна обрабатывать вызовы без аргументов с одним аргументом с именованными аргументами из строки, содержащей несколько команд с разделителями с запятой и с переменными в аргументах, включая строковые переменные, содержащие пространства. В принципе, он должен быть альтернативой ls.

В приведенных ниже тестах сравните myls и встроенный ls:

[ПРИМЕЧАНИЕ: выход удаляется и/или уплотняется для экономии места]

PS> md C:\p\d\x, C:\p\d\y, C:\p\d\"jay z"
PS> cd C:\p\d
PS> ls                                 # no args
PS> myls                               # pass
PS> cd ..
PS> ls d                               # one arg
PS> myls d                             # pass
PS> $a="A"; $z="Z"; $y="y"; $jz="jay z"
PS> $a; ls d; $z                       # multiple statements
PS> $a; myls d; $z                     # pass
PS> $a; ls d -Exclude x; $z            # named args
PS> $a; myls d -Exclude x; $z          # pass
PS> $a; ls d -Exclude $y; $z           # variables in arg-line
PS> $a; myls d -Exclude $y; $z         # pass
PS> $a; ls d -Exclude $jz; $z          # variables containing spaces in arg-line
PS> $a; myls d -Exclude $jz; $z        # FAIL!

Есть ли способ переписать myls, чтобы получить нужное поведение?

Короткий ответ: Да, это возможно. Плохая новость: для этого требуется код, который знает подробности параметров и других метаданных о функции, к которой вы хотите позвонить. Хорошие новости: не нужно писать все это. Эти метаданные доступны программно и существуют доступные модули, которые можно использовать для автоматического генерации скелетного прокси-кода (см. Ниже @Jaykul ответ). Я решил использовать модуль под названием "MetaProgramming" . После импортирования создание drop-in myls script мертво просто:

New-ProxyCommand ls > .\myls.ps1

Затем можно начать настройку вновь созданного myls.ps1 script, например:

  ...
  begin
  {
    Write-Output "hello!"              # <-- add this line
    try {
      $outBuffer = $null
  ...

Voila! Эта новая версия проходит все тесты.

Ответ 2

Создание обертки должным образом не так просто, к сожалению. Прежде всего, оператор splat должен быть предположительно применен к хэш-таблице (например, автоматический PSBoundParameters или другой), а не к массиву $args.

Есть как минимум два варианта (оба не идеальны) и хак (см. раздел обновления).

Вариант 1. Используйте "классический" способ создания обертки. Пример: посмотрите, как это делается для функции help, которая является оболочкой командлета Get-Help:

Get-Content function:\help

Вы можете видеть, что функция-оболочка объявляет all параметры, которые имеет завершенный командлет, чтобы иметь PSBoundParameters , ограниченный существующими параметрами функции.. p >

Ваш пример. Если ваша функция объявляет параметр Exclude, тогда примерный код начинает работать. Но он работает только для Exclude, а не Force, хотя Force также передается в:

function myls($Exclude) {
    # only Exclude is in $PSBoundParameters even though we send Force, too:
    $PSBoundParameters | Out-String | Out-Host
    Write-Output "hello!"
    Get-ChildItem @PSBoundParameters
}
cd d
myls -Exclude b -Force

Вариант 2. Теоретически в теории можно было бы построить хэш-таблицу из массива $args вручную и применить к ней оператор splat. Но эта задача не выглядит практически привлекательной.


UPDATE

Ну, на самом деле есть еще один вариант ad-hoc (чистый взлом!), который требует минимальных усилий и будет работать в некоторых тривиальных, в основном интерактивных сценариях. Это не для какого-то серьезного кода!

function myls {
    # extra job
    Write-Output "hello!"

    # invoke/repeat the calling code line with myls "replaced" with Get-ChildItem
    Set-Alias myls Get-ChildItem
    Invoke-Expression $MyInvocation.Line
}

cd d

# variables can be used as the parameter values, too
$exclude = 'b'

myls -Exclude $exclude -Force

Ответ 3

Я верю в это:

функция myls {Write-Output "привет!"; iex "Get-ChildItem @args" }

приблизится к ожидаемому результату.

Обновление. По-видимому, существует известная ошибка с использованием @args таким образом, чтобы передать именованные параметры:

https://connect.microsoft.com/PowerShell/feedback/details/368512/splat-operator-args-doesnt-properly-pass-named-arguments?wa=wsignin1.0

Я бы прекратил использовать это, пока не решит.