Овладение функциями PowerShell: Пошаговое руководство

Как только вы привыкнете к написанию сценариев PowerShell, вам нужно будет изучить модульность кода. Модульность – это просто модное слово для создания кода из строительных блоков. В мире PowerShell функции PowerShell – один из лучших способов сделать это.

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

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

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

Функции против командлетов

Концепция функции может звучать знакомо, потому что она немного напоминает командлеты, которые вы, вероятно, уже использовали. Команды, такие как Start-Service и Write-Host, например, похожи на функции. Это именованные куски кода, решающие одну проблему. Разница между функцией и командлетом заключается в том, как создается каждая из этих конструкций.

A cmdlet isn’t written with PowerShell. It’s written in another language, typically something like C#. The cmdlet is then compiled and made available inside PowerShell.

Функции, в отличие от командлетов, написаны на PowerShell, а не на другом языке.

Вы можете определить, является ли команда командлетом или функцией, используя командлет Get-Command и его параметр CommandType, как показано ниже

Get-Command –CommandType Function

Эта команда возвращает все функции, в данный момент загруженные в ваш сеанс PowerShell, или внутри модулей, доступных для PowerShell.

Связано: Понимание и создание модулей PowerShell

Предварительные требования

Если вы хотите следовать всем примерам, убедитесь, что у вас есть версия PowerShell. Для этого учебника нет конкретных требований к версии. Также убедитесь, что у вас есть удобный редактор кода, такой как Visual Studio Code, чтобы копировать, вставлять и запускать некоторые фрагменты кода.

Создание простой функции

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

Ниже вы можете увидеть пример простой функции и ее выполнение. Эта функция называется Install-Software и использует команду Write-Host для отображения сообщения в консоли. После определения функции вы можете использовать ее имя для выполнения кода внутри блока команд.

PS> function Install-Software { Write-Host 'I installed some software. Yippee!' }
PS> Install-Software
I installed some software, Yippee!

Именование с использованием глагола и существительного

A function’s name is important. You can name your functions whatever you want, but the name should always describe what the function does. The function-naming convention in PowerShell is the Verb-Noun syntax.

Всегда начинайте имя функции с глагола, за которым следует дефис и существительное. Чтобы получить список “одобренных” глаголов, используйте команду Get-Verb.

Изменение поведения функции

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

PS> function Install-Software { Write-Host 'You installed some software, Yay!' }
PS> Install-Software
You installed some software, Yay!

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

Определение расширенной функции

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

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

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

I suggest you now open your favorite editor and store the function in a .ps1 file as you work through the rest of the tutorial.

Добавление параметров к функциям

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

Связано: Все, что вы хотели знать о параметрах PowerShell

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

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

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

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

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

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

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

function Install-Software {
	[CmdletBinding()]
	param()

	Write-Host 'I installed software version 2. Yippee!'
}

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

В настоящее время функция фактически не устанавливает программное обеспечение. Она просто использует командлет Write-Host для имитации установки программного обеспечения, чтобы вы могли сосредоточиться на написании функции.

После того как вы добавили блок param, вы можете создать параметр, поместив его в скобки блока param, как показано ниже.

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string] $Version
	)
	
	Write-Host "I installed software version $Version. Yippee!"

}

Внутри блока param, указанного выше, вы сначала определите блок Parameter. Использование блока Parameter() таким образом превратит его в “расширенный параметр”. Пустой блок Parameter, как здесь, ничего не делает, но он обязателен; я объясню, как его использовать в следующем разделе.

Давайте сосредоточимся на типе [string] перед именем параметра. Размещая тип параметра между квадратными скобками перед именем переменной параметра, вы приводите значение параметра к определенному типу. PowerShell всегда будет пытаться преобразовать любое значение, переданное этому параметру, в строку, если оно еще не является таковым. В приведенном выше примере любое переданное значение для $Version всегда будет рассматриваться как строка.

Приведение вашего параметра к типу не обязательно, но я настоятельно рекомендую это. Это явно определяет тип и существенно снижает ошибки в будущем.

Теперь вы также добавляете $Version в ваше выражение Write-Host. Это означает, что при выполнении функции Install-Software с параметром Version и передаче ему номера версии, вы должны получить соответствующее утверждение, как показано ниже.

PS> Install-Software -Version 2
I installed software version 2. Yippee!

Давайте посмотрим, что можно сделать с этим параметром сейчас.

Обязательный атрибут параметра

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

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

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Write-Host "I installed software version $Version. Yippee!"

}

Если вы запустите функцию выше, вы должны получить следующий запрос:

Prompting for a mandatory parameter

После установки обязательного атрибута выполнение функции без параметра будет приостановлено до тех пор, пока пользователь не введет значение. Функция теперь ожидает, пока пользователь не укажет значение для параметра “Версия”. После ввода PowerShell выполнит функцию.

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

Install-Software -Version 2

Значения параметров по умолчанию

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

Например, если вы хотите установить версию 2 этого программного обеспечения в 90% случаев, и вы не хотите каждый раз устанавливать значение при запуске функции, вы можете присвоить параметру “$Version” значение по умолчанию 2. Ниже приведен пример.

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string]$Version = 2
	)

	Write-Host "I installed software version $Version. Yippee!"

}

Наличие значения параметра по умолчанию не мешает передаче собственного значения. Переданное значение будет переопределять значение по умолчанию.

Добавление атрибутов проверки параметров

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

Ограничение информации, которую пользователи (или даже вы!) могут передавать в ваши функции или сценарии, позволит устранить ненужный код внутри вашей функции. Например, предположим, что вы передаете значение 3 в функцию “Установить программное обеспечение”, зная, что версия 3 является существующей.

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

Жизнь без проверки параметров

Посмотрите на пример ниже, когда вы используете строку $Version в пути к файлу. Если кто-то передает значение, которое не соответствует существующему имени папки (например, SoftwareV3 или SoftwareV4), код не выполнится.

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}
Failing on unexpected parameter values

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

Добавление проверки параметров

Существует различные виды проверки параметров, но в отношении вашей функции Install-Software лучше всего подходит атрибут [ValidateSet](https://adamtheautomator.com/powershell-validateset/). Атрибут проверки ValidateSet позволяет указать список допустимых значений для параметра. Если вы учитываете только строку 1 или 2, вы должны убедиться, что пользователь может указать только эти значения, в противном случае функция сразу завершится и сообщит пользователю о причине.

Добавьте атрибуты проверки параметров внутри блока param, прямо под оригинальным блоком Parameter, как показано ниже.

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}

Добавив набор элементов (1 и 2) внутри закрывающих скобок атрибута ValidateSet, это указывает PowerShell, что допустимыми значениями для Version являются только 1 или 2. Если пользователь попытается передать что-то, кроме того, что есть в наборе, он получит сообщение об ошибке, как показано ниже, уведомляя его о том, что у него есть только определенное количество вариантов.

Parameter validation stopping PowerShell function execution

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

Принятие ввода через канал

До сих пор вы создали функцию с параметром, который можно передать только с использованием типичного синтаксиса -ParameterName <Value>. Это работает, но у вас также есть другой вариант передачи значений параметров с использованием канала PowerShell. Давайте добавим возможности канала к нашей функции Install-Software.

Связано: Принятие ввода через канал в статье о параметрах ATA PowerShell

Сначала добавьте еще один параметр в свой код, который указывает компьютер, на котором вы хотите установить программное обеспечение. Также добавьте этот параметр к вашей ссылке Write-Host, чтобы имитировать установку.

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
		[ValidateSet('1','2')],
		
		[Parameter(Mandatory)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

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

$computers = @("SRV1", "SRV2", "SRV3")
foreach ($pc in $computers) {
	Install-Software -Version 2 -ComputerName $pc
}

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

Сделать Функцию Совместимой с Конвейером

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

A PowerShell function uses two kinds of pipeline input: ByValue (entire object) and ByPropertyName (a single object property). Here, because the $computers array contains only strings, you’ll pass those strings via ByValue.

Чтобы добавить поддержку конвейера, добавьте атрибут параметра к параметру, который вы хотите, используя одно из двух ключевых слов: ValueFromPipeline или ValueFromPipelineByPropertyName, как показано ниже.

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

После того как вы загрузили обновленную функцию Install-Software, вызовите ее так:

$computers = @("SRV1", "SRV2", "SRV3")
$computers | Install-Software -Version 2

Запустите сценарий снова, и вы должны получить что-то вроде этого:

I installed software version 2 on SRV3. Yippee!

Обратите внимание, что Install-Software выполняется только для последней строки в массиве. Вы увидите, как это исправить в следующем разделе.

Добавление блока process

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

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	process {
		Write-Host "I installed software version $Version on $ComputerName. Yippee!"
	}
}

Теперь вызовите функцию еще раз, так же, как вы делали ранее. Функция Install-Software теперь вернет три строки (по одной для каждого объекта).

I installed software version 2 on SRV1. Yippee!
I installed software version 2 on SRV2. Yippee!
I installed software version 2 on SRV3. Yippee!

Блок process содержит основной код, который вы хотите выполнить. Вы также можете использовать блоки begin и end для кода, который будет выполняться в начале и в конце вызова функции. Вы можете узнать больше о блоках begin, process и end через документацию Microsoft.

Следующие шаги

Функции позволяют вам разделить код на отдельные строительные блоки. Они не только помогают вам разбить вашу работу на более мелкие, более управляемые части, но и заставляют вас писать читаемый и тестируемый код.

I now challenge you to look through some old scripts and see where you can add a PowerShell function. Look for patterns in code. Build a function from those patterns. Notice where you’re copying/pasting code snippets and turn those into PowerShell functions!

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