Параметры PowerShell: Разблокируйте силу сценариев

Все команды PowerShell могут иметь один или несколько параметров, иногда их называют аргументами. Если вы не используете параметры PowerShell в своих функциях PowerShell, вы не пишете хороший код на PowerShell!

В этой статье вы узнаете практически обо всех аспектах создания и использования параметров или аргументов в PowerShell!

Это пример из моей книги “PowerShell для системных администраторов”. Если вы хотите выучить PowerShell или узнать несколько хитростей профессии, проверьте это!

Зачем вам нужен параметр?

Когда вы начинаете создавать функции, у вас есть возможность включить параметры или нет, и определить, как эти параметры будут работать.

Допустим, у вас есть функция, которая устанавливает Microsoft Office. Возможно, она вызывает установщик Office бесшумно внутри функции. Что делает функция, не имеет значения для наших целей. Базовая функция выглядит так с именем функции и блоком сценария.

function Install-Office {
    ## запустите бесшумный установщик здесь
}

В этом примере вы просто запустили Install-Office без параметров, и он делает свою работу.

Не имеет значения, были ли у функции Install-Office параметры или нет. По-видимому, у нее не было обязательных параметров; в противном случае PowerShell не позволил бы нам запустить ее без использования параметра.

Когда использовать параметр PowerShell

В Office существует множество различных версий. Возможно, вам нужно установить Office 2013 и 2016. В настоящее время у вас нет способа указать это. Вы могли бы изменять код функции каждый раз, когда хотите изменить поведение.

Например, вы могли бы создать две отдельные функции для установки разных версий.

function Install-Office2013 {
    Write-Host 'I installed Office 2013. Yippee!'
}

function Install-Office2016 {
    Write-Host 'I installed Office 2016. Yippee!'
}

Это работает, но это не масштабируемо. Это заставляет вас создавать отдельную функцию для каждой версии Office, которая выходит. Вам придется дублировать много кода, когда это не нужно.

Вместо этого вам нужен способ передавать разные значения во время выполнения, чтобы изменить поведение функции. Как это сделать?

Да! Параметры или то, что некоторые люди называют аргументами.

Поскольку мы хотели бы устанавливать разные версии Office без изменения кода каждый раз, вы должны добавить по крайней мере один параметр к этой функции.

Прежде чем быстро придумать параметр PowerShell для использования, важно сначала задать себе вопрос; “Какое минимальное изменение или изменения, по вашему мнению, потребуются в этой функции?”.

Помните, что вам нужно будет перезапустить эту функцию без изменения какого-либо кода внутри функции. В этом примере параметр, вероятно, очевиден для вас; вам нужно добавить параметр Version. Но, когда у вас есть функция с десятками строк кода, ответ может быть не так очевиден. Пока вы отвечаете на этот вопрос как можно точнее, это всегда поможет.

Итак, вы знаете, что вам нужен параметр Version. Что теперь? Теперь вы можете добавить его, но, как и в любом отличном языке программирования, существует несколько способов решить эту задачу.

В этом уроке я покажу вам “лучший” способ создания параметров на основе моего почти десятилетнего опыта работы с PowerShell. Однако помните, что это не единственный способ создания параметра.

Существуют также позиционные параметры. Они позволяют передавать значения параметрам, не указывая имя параметра. Позиционные параметры работают, но не считаются “лучшей практикой”. Почему? Потому что их труднее читать, особенно если у вас определено много параметров для функции.

Создание простого параметра PowerShell

Создание параметра для функции требует двух основных компонентов: блока param и самого параметра. Блок param определяется ключевым словом param, за которым следует набор круглых скобок.

An example of a param block
function Install-Office { [CmdletBinding()] param() Write-Host 'I installed Office 2016. Yippee!' }

На этом этапе реальная функциональность функции не изменилась ни на йоту. Мы просто подготовили некоторую инфраструктуру, готовясь к созданию первого параметра.

Как только у нас есть блок param, вы создадите параметр. Метод, который я предлагаю вам для создания параметра, включает блок Parameter, за которым следует тип параметра, за которым следует переменная параметра ниже.

An example of a param block
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host 'I installed Office 2016. Yippee!' }

Теперь у нас есть созданный параметр функции в PowerShell, но что произошло здесь на самом деле?

Блок Parameter является необязательной, но рекомендуемой частью каждого параметра. Как и блок param, это “сантехника функции”, которая готовит параметр к добавлению дополнительной функциональности. Вторая строка – это то место, где вы определяете тип параметра.

В данном случае мы решили привести параметр Version к строковому типу. Определение явного типа означает, что любое значение, переданное этому параметру, всегда будет пытаться быть “преобразованным” в строку, если оно еще не является таковым.

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

Теперь, когда вы определили параметр, вы можете выполнить команду Install-Office с передачей строки версии параметру Version, например, как 2013. Значение, переданное параметру Version, иногда называется аргументами или значениями параметров.

Passing a Parameter to the Function
PS> Install-Office -Version 2013 I installed Office 2016. Yippee!

Что здесь происходит вообще? Вы сказали, что хотите установить версию 2013, но он все равно сообщает вам, что установлена версия 2016. Когда вы добавляете параметр, необходимо помнить также изменить код функции на переменную. Когда параметр передается функции, эта переменная будет раскрыта в то значение, которое было передано.

Измените статический текст 2016 и замените его переменной параметра Version, преобразовав одинарные кавычки в двойные, чтобы переменная раскрывалась.

Modifying function code account for a parameter
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" }

Теперь вы можете видеть, что любое значение, переданное параметру Version, затем будет передано в функцию как переменная $Version.

Атрибут обязательного параметра

Вспомните, что я упоминал о строке [Parameter()], как о “системной сантехнике” и необходимости подготовить функцию для дальнейшей работы? Добавление атрибутов параметра к параметру – это тот дополнительный этап, о котором я ранее говорил.

A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.

Например, одним из наиболее распространенных атрибутов параметра, который вы установите, является ключевое слово Mandatory. По умолчанию вы могли бы вызывать функцию Install-Office без использования параметра Version, и она выполнялась бы нормально. Параметр Version был бы необязательным. Конечно, он не расширил бы переменную $Version внутри функции из-за отсутствия значения, но функция всё равно бы выполнилась.

Часто при создании параметра вы захотите, чтобы пользователь всегда использовал этот параметр. Вы будете зависеть от значения параметра где-то в коде функции, и если параметр не передан, функция завершится ошибкой. В таких случаях вы хотите, чтобы пользователь обязательно передавал значение этому параметру в вашу функцию. Вы хотите, чтобы этот параметр стал обязательным.

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

Using a mandatory parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" } PS> Install-Office cmdlet Install-Office at command pipeline position 1 Supply values for the following parameters: Version:

Функция будет ждать, пока вы укажете значение для параметра Version. После этого, нажав Enter, PowerShell выполнит функцию и продолжит выполнение. Если вы предоставите значение для параметра, PowerShell не будет запрашивать его каждый раз.

Атрибуты проверки параметров в PowerShell

Сделать параметр обязательным – один из наиболее распространенных атрибутов параметров, которые можно добавить, но также можно использовать атрибуты проверки параметров. В программировании всегда важно ограничивать ввод пользователя как можно тщательнее. Ограничение информации, которую пользователи (или даже вы сами!) могут передавать вашим функциям или скриптам, позволит избежать лишнего кода внутри вашей функции, который должен учитывать все возможные ситуации.

Изучение на примерах

Например, в функции Install-Office я продемонстрировал передачу значения 2013, потому что знал, что это сработает. Я написал этот код! Я предполагаю (никогда не делайте этого в коде!) что очевидно, что любой, кто что-то знает, укажет версию либо 2013, либо 2016. Ну, что очевидно для вас, может быть не так очевидно для других людей.

Если хотите быть техничным относительно версий, наверное, было бы более точно указать версию 2013 как 15.0, а 2016 как 16.0, если бы Microsoft все еще использовала схему версионирования, которую они использовали ранее. Но что, если, предполагая, что они укажут версию 2013 или 2016, в вашем коде внутри функции будет что-то, что ищет папки с этими версиями или что-то еще?

Ниже приведен пример, где вы можете использовать строку $Version в пути к файлу. Если кто-то передает значение, которое не завершает имя папки Office2013 или Office2016, это приведет к ошибке или, что еще хуже, удалению непредвиденных папок или изменению вещей, которые вы не учли.

Assuming Parameter Values
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version '15.0' Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist. At line:7 char:5 Get-ChildItem -Path "\\SRV1\Installers\Office$Version" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep tion FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Чтобы ограничить пользователя вводом ожидаемых значений, вы можете добавить некоторую валидацию параметров PowerShell.

Используйте атрибут валидации параметров ValidateSet

Существует несколько видов валидации параметров, которые вы можете использовать. Для полного списка выполните Get-Help about_Functions_Advanced_Parameters. В этом примере, атрибут ValidateSet вероятно будет наилучшим выбором.

Атрибут валидации ValidateSet позволяет указать список значений, разрешенных в качестве значения параметра. Поскольку мы учитываем только строки 2013 или 2016, я хочу убедиться, что пользователь может указывать только эти значения. В противном случае функция сразу завершится с уведомлением о причине.

Вы можете добавить атрибуты валидации параметров прямо под ключевым словом Parameter. В этом примере в скобках атрибута параметра у вас есть массив элементов; 2013 и 2016. Атрибут валидации параметра сообщает PowerShell, что допустимыми значениями для Version являются только 2013 или 2016. Если вы попытаетесь передать что-то, кроме того, что есть в наборе, вы получите ошибку с уведомлением о том, что у вас есть только определенное количество вариантов.

Using the ValidateSet parameter validation attribute
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version 15.0 Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set "2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:25 Install-Office -Version 15.0 ~~~~ CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office

Атрибут ValidateSet – общий атрибут валидации. Для полного разбора всех способов ограничения значений параметров ознакомьтесь с темой справки Functions_Advanced_Parameters, запустив Get-Help about_Functions_Advanced_Parameters.

Наборы параметров

Предположим, вы хотите, чтобы некоторые параметры PowerShell использовались только с другими параметрами. Возможно, вы добавили параметр Path к функции Install-Office. Этот путь установит любую версию установщика. В этом случае вы не хотите, чтобы пользователь использовал параметр Version.

Вам нужны наборы параметров.

Параметры могут быть сгруппированы в наборы, которые могут использоваться только с другими параметрами из того же набора. Используя функцию ниже, теперь можно использовать как параметр Version, так и параметр Path, чтобы получить путь к установщику.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Однако это создает проблему, поскольку пользователь может использовать оба параметра. Кроме того, поскольку оба параметра обязательны, им придется использовать оба, когда это не требуется. Чтобы исправить это, мы можем поместить каждый параметр в набор параметров, как показано ниже.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ParameterSetName = 'ByVersion')]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory, ParameterSetName = 'ByPath')]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Определив имя набора параметров для каждого параметра, вы можете контролировать группы параметров вместе.

Набор параметров по умолчанию

Что, если пользователь попытается запустить Install-Office без параметров? Это не предусмотрено, и вы увидите дружественное сообщение об ошибке.

No parameter set

Чтобы исправить это, вам нужно определить набор параметров по умолчанию в области CmdletBinding(). Это указывает функции выбирать набор параметров для использования, если явно не указаны параметры, изменяя [CmdletBinding()] на [CmdletBinding(DefaultParameterSetName = 'ByVersion')]

. Теперь, когда вы запускаете Install-Office, он запросит параметр Version, поскольку будет использовать этот набор параметров.

Ввод через конвейер

В предыдущих примерах вы создавали функции с параметром PowerShell, который можно передавать только с использованием типичного синтаксиса -ParameterName Value. Но, как вы уже узнали, в PowerShell есть интуитивный конвейер, который позволяет вам без применения “типичного” синтаксиса легко передавать объекты от одной команды к другой.

При использовании конвейера вы “цепляете” команды вместе с символом трубы |, что позволяет пользователю отправлять вывод команды, например, Get-Service, к Start-Service в качестве ярлыка для передачи параметра Name в Start-Service.

«Старый» способ с использованием цикла

В вашей пользовательской функции, с которой вы работаете, вы устанавливаете Office и имеете параметр Version. Допустим, у вас есть список имен компьютеров в файле CSV в одной строке с версией Office, которую нужно установить на них, во второй строке. Файл CSV выглядит примерно так:

ComputerName,Version
PC1,2016
PC2,2013
PC3,2016

Вы хотите установить версию Office, которая находится рядом с каждым компьютером, на этот компьютер.

Во-первых, вам нужно добавить параметр ComputerName в функцию, чтобы передавать разные имена компьютеров для каждой итерации функции. Ниже приведен псевдокод, представляющий код, который может находиться в вымышленной функции, а также добавлен экземпляр Write-Host, чтобы увидеть, как расширяются переменные внутри функции.

Adding the ComputerName parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version, [Parameter()] [string]$ComputerName ) <# ## Подключение к удаленному компьютеру с использованием некоторого кода здесь Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Выполнение операций по установке версии Office на этот компьютер Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

После добавления параметра ComputerName в функцию, вы можете сделать это, считав файл CSV и передавая значения для имени компьютера и версии функции Install-Office.

$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
    Install-Office -Version $_.Version -ComputerName $_.ComputerName
}

Создание конвейера ввода для параметров

Этот метод чтения строк CSV и использования цикла для передачи свойств каждой строки в функцию является “старым” способом. В этом разделе вы хотите полностью отказаться от цикла foreach и вместо этого использовать конвейер.

Как есть, функция вообще не поддерживает конвейер. Было бы логично предположить, что вы можете передать каждое имя компьютера и версию функции с использованием конвейера. Ниже мы читаем CSV и непосредственно передаем его в Install-Office, , но это не работает.

No function pipeline input defined
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office

Вы можете предполагать всё, что угодно, но это не делает его работающим. Нас подсказывают о параметре Version, когда вы знаете, что Import-Csv отправляет его как свойство объекта. Почему это не работает? Потому что вы ещё не добавили поддержку конвейера.

В функции PowerShell существует два типа ввода из конвейера; ByValue (весь объект) и ByPropertyName (одно свойство объекта). Какой из них, по вашему мнению, лучший способ объединить вывод Import-Csv с вводом Install-Office?

Без какого-либо рефакторинга вы можете использовать метод ByPropertyName, поскольку, в конце концов, Import-Csv уже возвращает свойства Version и ComputerName, так как они являются столбцами в CSV.

Добавление поддержки конвейера к пользовательской функции гораздо проще, чем вы думаете. Это всего лишь атрибут параметра, представленный одним из двух ключевых слов; ValueFromPipeline или ValueFromPipelineByPropertyName.

В этом примере вы хотите привязать свойства ComputerName и Version, возвращаемые из Import-Csv, к параметрам Version и ComputerName Install-Office, поэтому вы будете использовать ValueFromPipelineByPropertyName.

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

Adding Pipeline Support
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) <# ## Подключитесь к удаленному компьютеру с помощью некоторого кода здесь Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Выполните действия по установке версии офиса на этот компьютер Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

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

Не Забудьте Блок Процесса!

Когда вам нужно создать функцию, которая включает поддержку конвейера, вы должны включить (как минимум) “встроенный” блок внутри вашей функции, называемый. process. Этот блок процесса сообщает PowerShell, что при получении входных данных из конвейера нужно выполнять функцию для каждой итерации. По умолчанию будет выполнена только последняя.

Технически вы можете добавить другие блоки, такие как begin и end, но скриптеры не используют их так часто.

Чтобы указать PowerShell выполнять эту функцию для каждого объекта, поступающего на вход, я добавлю блок process, который включает в себя код внутри него.

Adding the process block
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) process { <# ## Подключитесь к удаленному компьютеру с помощью некоторого кода здесь Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Выполните действия по установке версии офиса на этот компьютер Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" } }

Теперь вы можете видеть, что свойства Version и ComputerName каждого объекта, возвращенные из Import-Csv, были переданы в Install-Office и привязаны к параметрам Version и ComputerName.

Ресурсы

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

I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.

Source:
https://adamtheautomator.com/powershell-parameter/