Contents of this directory is archived and no longer updated.

Работая с функциями в PowerShell можно столкнуться с одной особенностью – в качестве передачи аргументов в функции или скрипты вы можете использовать почти всё, кроме ключей (данных типа Switch). Их передавать можно, но тут есть одна особенность. Обычно это ощущается, когда вы работаете с командлетами. Возьмём простой пример:

function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
Write-Host '$Path is:' $Path
Write-Host '$Filter is:' $Filter
Write-Host '$Force is:' $Force
}

и вызовем эту функцию:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>> Write-Host '$Path is:' $Path
>> Write-Host '$Filter is:' $Filter
>> Write-Host '$Force is:' $Force
>> }
>>
[↓] [vPodans] Test C:\ * -force
$Path is: C:\
$Filter is: *
$Force is: True
[↓] [vPodans]

В принципе, всё как и ожидалось. Но если внимательно посмотреть на последний аргумент, то мы увидим лишь True, т.е. увидим факт, что ключ Force был передан. Однако, PowerShell не умеет подставять (биндить) переменную $Force (равно как и другие переменные) как именованный параметр в другую команду. Чтобы в этом убедиться, мы попробуем сымитировать нашу функцию как командлет Get-ChildItem:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>> Get-ChildItem $Path $Filter $Force
>> }
>>
[↓] [vPodans] Test C:\ * -Force
Get-ChildItem : A positional parameter cannot be found that accepts argument 'True'.
At line:2 char:14
+ Get-ChildItem <<<<  $Path $Filter $Force
    + CategoryInfo          : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

[↓] [vPodans]

Как видно, командлет Get-ChildItem не смог сопоставить последний аргумент ни с одним из своих параметров. Т.е. по факту выполнилась следующая строка:

Get-ChildItem -Path C:\ -Filter * True

PowerShell не смог сказать командлету, что мы указали ключ Force и хотим именно его передать в командлет. По факту в этом коде Get-ChildItem не знал, какие параметры ему были переданы и подставлял их на основе номера позиции параметра. А т.к. у Get-ChildItem нету параметра с порядковым номером 3 (с номером 1 идёт Path или LiteralPath, а с номером 2 идёт Filter. Остальные параметры именованные), то мы получили ошибку. Но всё же, как выкручиваться из этой ситуации? Вы можете как угодно пытаться подставить аргументы, но ничего не выйдет. Для этих целей в PowerShell V2 появилась специальная переменная - $PSBoundParameters. Эта переменная по сути представляет собой хэш-таблицу:

[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) {
>> $PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force

Key                                                         Value
---                                                         -----
Force                                                       True
Path                                                        C:\
Filter                                                      *


[↓] [vPodans]

В отличии от первого примера переменная $PSBoundParameters содержит не только значения переменных, но и их имена (в перовм примере я вручную дописывал имена переменных), которые используются в качестве именованных параметров. Т.е. при подстановке аргументов в команду, она сначала выбирает имя переменной в качестве именованного параметра и значение переменной подставляет в качестве аргумента этого параметра. Синтаксис использования этой переменной очень прост:

function Name ($arg1, $arg2, $arg3 ... $argN) {
Command @PSBoundParameters
}

и в результате будет исполняться вот такая команда:

Command –arg1 <значение $arg1> –arg2 <значение $arg2> –arg3 <значение $arg3> … –argN <значение $argN>

Т.е. будут подставляться именованные параметры и значения переменных соответствующих аргументов. Давайте проверим, как это подействует на наш пример с Get-ChildItem:

function Test ([string]$Path, [String]$Filter, [switch]$Force) {
Get-ChildItem $PSBoundParameters
}

[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) {
>> Get-ChildItem @PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force


    Directory: C:\


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d--hs       10.07.2007.     19:44            $Recycle.Bin
d--hs       09.03.2008.     17:53            Boot
d----       23.11.2008.     19:41            inetpub
d-rh-       26.06.2007.     22:27            MSOCache
d----       09.03.2008.     17:41            PerfLogs
<...>

И вы можете видеть, что у нас всё получилось в наилучшем виде! В сравнении с предыдущим примером фактически выполнилась следующая команда:

Get-ChildItem -Path C:\ -Filter * -Force

Для формирования аргументов для командлетов в функциях (так называемые wrapped cmdlets) это самый красивый и идеальный вариант. Если вы захотите изменить логику стандартных командлетов или добавить в них свой функционал, то $PSBoundParamters сделает за вас очень много лишней работы. Но это не единственное полезное применение для этой переменной. Она так же позволяет сократить возможность ошибки при вызове функции внутри скрипта или другой функции, которая принимает те же аргументы. Давайте посмотрим ещё один пример:

function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
    Write-Host '$Path in Test is:' $Path
    Write-Host '$Filter in Test is:' $Filter
    Write-Host '$Force in Test is:' $Force
    Write-Host --------------------------------
    function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
Write-Host '$Path in Test2 is:' $Path
Write-Host '$Filter in Test2 is:' $Filter Write-Host '$Force in Test2 is:' $Force } Test2 @PSBoundParameters }

Что мы делаем: мы создали функцию Test, которая принимает набор аргументов. Внутри этой функции есть другая функция Test2, которая принимает те же аргументы. Далее из функции Test вызываем функцию Test2 и с помощью $PSBoundParameters передаём в неё аргументы. Обратите внимание, что в функции Test2 я изменил порядок аргументов. Это сделано для того, чтобы показать, что $PSBoundParamters отсортирует наши аргументы. А теперь внимание на экран:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>>     Write-Host '$Path in Test is:' $Path
>>     Write-Host '$Filter in Test is:' $Filter
>>     Write-Host '$Force in Test is:' $Force
>>     Write-Host --------------------------------
>>     function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
>>         Write-Host '$Path in Test2 is:' $Path
>>         Write-Host '$Filter in Test2 is:' $Filter
>>         Write-Host '$Force in Test2 is:' $Force
>>     }
>>     Test2 @PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force
$Path in Test is: C:\
$Filter in Test is: *
$Force in Test is: True
--------------------------------
$Path in Test2 is: C:\
$Filter in Test2 is: *
$Force in Test2 is: True
[↓] [vPodans]

И смотрите, что у нас получилось. А у нас получилось, что функция Test2 получила тот же набор аргументов, причём они были подставлены в правильном соответствии. Фактически строка Test2 @PSBoundParameters была преобразована в:

Test2 -Path $Path -Filter $Filter -Force $true

Т.е. в вариант, который мы вынуждены использовать в PowerShell 1.0. Плюс, мы имеем возможность подставлять позиционно ключи (объекты типа Switch), что есть очень позитивно.


Share this article:

Comments:

Comments are closed.