Поделиться через


Глава 9. Функции

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

Пишите функции, когда это возможно, так как они больше подходят для инструментальной задачи. Вы можете добавить функции в модуль скрипта, поместить этот модуль в расположение, определенное в $env:PSModulePath, и вызвать функции без необходимости найти место сохранения функций. С помощью модуля PowerShellGet легко предоставить общий доступ к модулям PowerShell в репозитории NuGet. PowerShellGet поставляется с PowerShell версии 5.0 и более поздней. Она также доступна как отдельная загрузка для PowerShell версии 3.0 и выше.

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

Именование

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

Get-Verb | Sort-Object -Property Verb

Свойство Group дает представление о том, как должны использоваться глаголы.

Verb        Group
----        -----
Add         Common
Approve     Lifecycle
Assert      Lifecycle
Backup      Data
Block       Security
Checkpoint  Data
Clear       Common
Close       Common
Compare     Data
Complete    Lifecycle
Compress    Data
Confirm     Lifecycle
Connect     Communications
Convert     Data
ConvertFrom Data
ConvertTo   Data
Copy        Common
Debug       Diagnostic
Deny        Lifecycle
Disable     Lifecycle
Disconnect  Communications
Dismount    Data
Edit        Data
Enable      Lifecycle
Enter       Common
Exit        Common
Expand      Data
Export      Data
Find        Common
Format      Common
Get         Common
Grant       Security
Group       Data
Hide        Common
Import      Data
Initialize  Data
Install     Lifecycle
Invoke      Lifecycle
Join        Common
Limit       Data
Lock        Common
Measure     Diagnostic
Merge       Data
Mount       Data
Move        Common
New         Common
Open        Common
Optimize    Common
Out         Data
Ping        Diagnostic
Pop         Common
Protect     Security
Publish     Data
Push        Common
Read        Communications
Receive     Communications
Redo        Common
Register    Lifecycle
Remove      Common
Rename      Common
Repair      Diagnostic
Request     Lifecycle
Reset       Common
Resize      Common
Resolve     Diagnostic
Restart     Lifecycle
Restore     Data
Resume      Lifecycle
Revoke      Security
Save        Data
Search      Common
Select      Common
Send        Communications
Set         Common
Show        Common
Skip        Common
Split       Common
Start       Lifecycle
Step        Common
Stop        Lifecycle
Submit      Lifecycle
Suspend     Lifecycle
Switch      Common
Sync        Data
Test        Diagnostic
Trace       Diagnostic
Unblock     Security
Undo        Common
Uninstall   Lifecycle
Unlock      Common
Unprotect   Security
Unpublish   Data
Unregister  Lifecycle
Update      Data
Use         Other
Wait        Lifecycle
Watch       Common
Write       Communications

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

Простая функция

Функция в PowerShell объявлена с ключевым словом function, за которым следует имя функции, а затем открывающая и закрывающая фигурная скобка ({ }). Код, выполняемый функцией, содержится в фигурных скобках.

function Get-Version {
    $PSVersionTable.PSVersion
}

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

Get-Version
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

При использовании универсального имени для функций, таких как Get-Version, это может привести к конфликтам именования. Команды по умолчанию, которые будут добавлены в будущем, или команды, которые могут написать другие, могут вступить с ними в конфликт. Используйте префиксы для части имени функций, содержащей существительное, чтобы предотвратить конфликты имен. Например, <ApprovedVerb>-<Prefix><SingularNoun>.

В следующем примере используется префикс PS.

function Get-PSVersion {
    $PSVersionTable.PSVersion
}

Кроме имени, эта функция идентична предыдущей.

Get-PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

Вы по-прежнему можете иметь конфликт имен даже при добавлении префикса в существительное. Мне нравится добавлять к именам моих функций префикс с моими инициалами. Разработайте стандарт и придерживайтесь его.

function Get-MrPSVersion {
    $PSVersionTable.PSVersion
}

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

Get-MrPSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

После загрузки в память вы можете увидеть функции на диске PSDrive Function.

Get-ChildItem -Path Function:\Get-*Version
CommandType     Name                                               Version
-----------     ----                                               -------
Function        Get-Version
Function        Get-PSVersion
Function        Get-MrPSVersion

Если вы хотите удалить эти функции из текущего сеанса, удалите их из функции PSDrive или закройте и снова откройте PowerShell.

Get-ChildItem -Path Function:\Get-*Version | Remove-Item

Убедитесь, что функции действительно удалены.

Get-ChildItem -Path Function:\Get-*Version

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

Remove-Module -Name <ModuleName>

Командлет Remove-Module удаляет модули PowerShell из памяти в текущем сеансе PowerShell. Он не удаляет их из системы или диска.

Параметры

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

Обратите внимание, что в следующей функции я использовал ComputerName, а не computer, ServerNameили Host для имени параметра. Использование ComputerName стандартизирует имя параметра, приводя его в соответствие с именем и регистром параметра, как в командлетах по умолчанию.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Следующая функция запрашивает все команды в системе и возвращает число с определенными именами параметров.

function Get-MrParameterCount {
    param (
        [string[]]$ParameterName
    )

    foreach ($Parameter in $ParameterName) {
        $Results = Get-Command -ParameterName $Parameter -ErrorAction SilentlyContinue

        [pscustomobject]@{
            ParameterName   = $Parameter
            NumberOfCmdlets = $Results.Count
        }
    }
}

Как вы видите в следующих результатах, 39 команд с параметром ComputerName. Нет команд с такими параметрами, как computer, ServerName, Hostили Machine.

Get-MrParameterCount -ParameterName ComputerName, Computer, ServerName,
    Host, Machine
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName               39
Computer                    0
ServerName                  0
Host                        0
Machine                     0

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

Оператор param позволяет определить один или несколько параметров. Запятая (,) разделяет определения параметров. Дополнительные сведения см. в разделе about_Functions_Advanced_Parameters.

Расширенные функции

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

Начните с функции Test-MrParameter, которая использовалась в предыдущем разделе.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Существует несколько различных способов просмотра общих параметров. Один способ — просмотр синтаксиса с Get-Command.

Get-Command -Name Test-MrParameter -Syntax

Обратите внимание, что функция Test-MrParameter не имеет общих параметров.

Test-MrParameter [[-ComputerName] <Object>]

Другой способ — углубиться в свойства параметров Get-Command.

(Get-Command -Name Test-MrParameter).Parameters.Keys
ComputerName

Добавьте атрибут CmdletBinding, чтобы превратить функцию в расширенную функцию.

function Test-MrCmdletBinding {

    [CmdletBinding()] # Turns a regular function into an advanced function
    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

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

Get-Command -Name Test-MrCmdletBinding -Syntax
Test-MrCmdletBinding [[-ComputerName] <Object>] [<CommonParameters>]

Углубленный анализ свойства параметров Get-Command показывает фактические имена параметров, включая общие.

(Get-Command -Name Test-MrCmdletBinding).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable

Поддержка SupportsShouldProcess

Атрибут SupportsShouldProcess добавляет параметры управления рисками WhatIf и Confirm. Эти параметры необходимы только для команд, которые вносят изменения.

function Test-MrSupportsShouldProcess {

    [CmdletBinding(SupportsShouldProcess)]
    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Обратите внимание, что теперь существуют параметры WhatIf и Confirm.

Get-Command -Name Test-MrSupportsShouldProcess -Syntax
Test-MrSupportsShouldProcess [[-ComputerName] <Object>] [-WhatIf] [-Confirm]
[<CommonParameters>]

Вы также можете использовать Get-Command, чтобы вернуть список фактических имен параметров, включая общие, а также WhatIf и Confirm.

(Get-Command -Name Test-MrSupportsShouldProcess).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
WhatIf
Confirm

Проверка параметров

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

Всегда укажите тип данных для переменных, используемых для параметров. В следующем примере String указывается в качестве типа данных для параметра ComputerName. Эта валидация ограничивает возможность указания только одного имени компьютера для параметра ComputerName.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [string]$ComputerName
    )

    Write-Output $ComputerName

}

Ошибка возникает, если указано несколько имен компьютера.

Test-MrParameterValidation -ComputerName Server01, Server02
Test-MrParameterValidation : Cannot process argument transformation on
parameter 'ComputerName'. Cannot convert value to type System.String.
At line:1 char:42
+ Test-MrParameterValidation -ComputerName Server01, Server02
+                                          ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Test-MrParameterValidation]
   , ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-MrP
   arameterValidation

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

Синтаксис, используемый в следующем примере, совместим с PowerShell версии 3.0 и выше. [Parameter(Mandatory=$true)] можно указать, чтобы сделать функцию совместимой с PowerShell версии 2.0 или более поздней.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$ComputerName
    )

    Write-Output $ComputerName

}

Теперь, когда требуется ComputerName, если он не указан, функция запрашивает его.

Test-MrParameterValidation
cmdlet Test-MrParameterValidation at command pipeline position 1
Supply values for the following parameters:
ComputerName:

Если вы хотите разрешить несколько значений для параметра ComputerName, используйте тип данных String, но добавьте квадратные скобки ([]) в тип данных, чтобы разрешить массив строк.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    Write-Output $ComputerName

}

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

Даже если задано значение по умолчанию, старайтесь не использовать статические значения. В следующем примере $env:COMPUTERNAME используется в качестве значения по умолчанию, которое автоматически преобразуется в имя локального компьютера, если значение не указано.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    Write-Output $ComputerName

}

Подробный вывод

Встроенные комментарии полезны, если вы пишете сложный код, но пользователи не видят их, если они не смотрят на код.

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

function Test-MrVerboseOutput {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    foreach ($Computer in $ComputerName) {
        #Attempting to perform an action on $Computer <<-- Don't use
        #inline comments like this, use write verbose instead.
        Write-Output $Computer
    }

}

Лучше использовать Write-Verbose вместо встроенных комментариев.

function Test-MrVerboseOutput {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    foreach ($Computer in $ComputerName) {
        Write-Verbose -Message "Attempting to perform an action on $Computer"
        Write-Output $Computer
    }

}

Подробные выходные данные не отображаются при вызове функции без параметра Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02

Подробные выходные данные отображаются при вызове функции с параметром Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose

Входные данные конвейера

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

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

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

function Test-MrPipelineInput {

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

    process {
        Write-Output $ComputerName
    }

}

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

function Test-MrPipelineInput {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
            Write-Output $ComputerName
    }

}

begin и end блоки являются необязательными. begin указывается перед блоком process и используется для выполнения любой начальной работы перед получением элементов из конвейера. Значения, которые передаются через канал, недоступны в блоке begin. Блок end указан после блока process и используется для очистки после обработки всех переданных элементов.

Обработка ошибок

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

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            Test-WSMan -ComputerName $Computer
        }
    }

}

В PowerShell существует несколько различных способов обработки ошибок. Try/Catch является более современным способом обработки ошибок.

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            try {
                Test-WSMan -ComputerName $Computer
            }
            catch {
                Write-Warning -Message "Unable to connect to Computer: $Computer"
            }
        }
    }

}

Хотя функция, показанная в предыдущем примере, использует обработку ошибок, она создает необработанное исключение, так как команда не создает завершающееся сообщение об ошибке. Перехватываются только завершающие ошибки. Укажите параметр ErrorAction со значением Stop, чтобы превратить непрерывающуюся ошибку в завершающую.

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            try {
                Test-WSMan -ComputerName $Computer -ErrorAction Stop
            }
            catch {
                Write-Warning -Message "Unable to connect to Computer: $Computer"
            }
        }
    }

}

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

Если вы используете что-то подобное .NET непосредственно из функции PowerShell, вы не можете указать параметр ErrorAction в самой команде. Перед вызовом метода .NET можно изменить переменную $ErrorActionPreference.

Справка на основе комментариев

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

function Get-MrAutoStoppedService {

<#
.SYNOPSIS
    Returns a list of services that are set to start automatically, are not
    currently running, excluding the services that are set to delayed start.

.DESCRIPTION
    Get-MrAutoStoppedService is a function that returns a list of services
    from the specified remote computer(s) that are set to start
    automatically, are not currently running, and it excludes the services
    that are set to start automatically with a delayed startup.

.PARAMETER ComputerName
    The remote computer(s) to check the status of the services on.

.PARAMETER Credential
    Specifies a user account that has permission to perform this action. The
    default is the current user.

.EXAMPLE
     Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'

.EXAMPLE
     'Server1', 'Server2' | Get-MrAutoStoppedService

.EXAMPLE
     Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credential)

.INPUTS
    String

.OUTPUTS
    PSCustomObject

.NOTES
    Author:  Mike F. Robbins
    Website: https://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding()]
    param (

    )

    #Function Body

}

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

Весь синтаксис для написания функции в PowerShell может показаться подавляющим для кого-то, кто начинает работу. Если вы не можете помнить синтаксис для чего-то, откройте второй экземпляр интегрированной среды сценариев PowerShell (ISE) на отдельном мониторе и просмотрите фрагмент кода "Командлет (расширенная функция) — завершить" при вводе кода для функций. Фрагменты кода можно получить в PowerShell ISE с помощью сочетания клавиш Ctrl + J.

Сводка

В этой главе вы узнали основы написания функций в PowerShell, в том числе как:

  • Создание расширенных функций
  • Использование проверки параметров
  • Использование подробных выходных данных
  • Поддержка входа конвейера
  • Обработка ошибок
  • Создать справку на основе комментариев

Обзор

  1. Как получить список утвержденных глаголов в PowerShell?
  2. Как превратить функцию PowerShell в расширенную функцию?
  3. Когда следует добавить параметры WhatIf и Confirm в функции PowerShell?
  4. Как превратить не завершающую выполнение ошибку в ошибку, приводящую к завершению?
  5. Зачем добавлять справочную информацию на основе комментариев в функции?

Ссылки