Contents of this directory is archived and no longer updated.

Мы с Васей Гусевым задумали утопичную идею составить какой-то набор гæдлайнов как правильно писать скрипты для PowerShell. Так исторически сложилось, что каждый пишет скрипты так, как он хочет/удобней/умеет. PowerShell один из самых новых языков скриптования (в отличии от vbs/cmd) и он внёс совершенно новый синтаксис и идею оформления кода, который как правило несовместим с жизнью с другими языками.

Я повидал множество скриптов, написанных разными пользователями и на основании их мы будем стараться разбирать наиболее частые ошибки в оформлении кода и прививать людям какие-то общие правила, как надо писать скрипты. Это связано с тем, что подавляющее количество ваших скриптов будет читаться кем-то ещё (например, вашим коллегой, боссом, etc.). И правильно оформленные скрипты упрощают и ускоряют его чтение и понимание.

Итак, сегодняшняя тема (посты в категории guildelines не будут последовательными) — как правильно реализовывать специальные параметры –Force, –WhatIf, –Confirm –Verbose и –Debug в скриптах и функциях.

Параметры Force, WhatIf и Confirm

  • Force

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

  • WhatIf

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

Remove-Item C:\Windows\notepad.exe -WhatIf

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

  • Confirm

Если ваш скрипт выполняет какие-то серьёзные и/или деструктивные действия (например, как командлет Set-ExecutionPolicy), следует применять параметр Confirm.

Определение этих параметров в коде

Применение этих параметров в простых функциях (не advanced) по сути сводится к написанию собственных обработчиков этих параметров. В Advanced functions появилась возможность универсально обрабатывать эти параметры. Вот пример кода:

function Remove-File {
[CmdletBinding(
    ConfirmImpact = 'High',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Path,
        [switch]$Force
    )
    if ($Force -or $PSCmdlet.ShouldProcess($Path,"Delete file")) {
        Remove-Item $Path
    }
}

И вот вывод какое у нас получится поведение:

[↓] [vPodans] Remove-File .\Desktop\qsca.txt

Confirm
Are you sure you want to perform this action?
Performing operation "Delete file" on Target ".\Desktop\qsca.txt".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): n
[↓] [vPodans] Remove-File .\Desktop\qsca.txt -WhatIf
What if: Performing operation "Delete file" on Target ".\Desktop\qsca.txt".
[↓] [vPodans] Remove-File .\Desktop\qsca.txt -Force
[↓] [vPodans]

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

Как мы добились такого поведения? Во-первых, конструкция в начале скрипта [CmdletBinding()] определяет общее поведение всего скрипта. У него есть разные атрибуты. В данном случае были использованы:

  • SupportsShouldProcess

Этот атрибут указывает, должен ли код ожидать ответ от пользователя на выполнение операции. Т.е. он включает обработчик для параметров –Confirm и –WhatIf.

  • ConfirmImpact

Этот атрибут указывает уровень воздействия кода на систему и включает обработчик параметра –Confirm. Возможные значения:

  1. None (или атрибут не указан вообще) — никаких сообщений подтверждения операции выводиться не будет. Даже если вы явно укажете параметр Confirm.
  2. Low — код оказывает минимальное воздействие на систему с минимальным риском потери данных.
  3. Medium — код оказывает среднее воздействие с некоторым риском потерять данные или произвести деструктивные действия.
  4. High — выполняемый код обладает высоким риском потери данных. Например, уровень High выставлен у командлета Set-ExecutionPolicy.

По умолчанию в сессии PowerShell уровень воздействия выставлен в High (можно посмотреть в переменной $ConfirmPreference). И все командлеты, у которых уровень воздействия на систему выше или такой же, как в $ConfirmPreference, запрос на подтверждение будет выводиться всегда.

Например, в сессии у нас $ConfirmPreference = 'High', а у командлета/функции тоже 'High' будет выведен запрос подтверждения. Как в случае с Set-ExecutionPolicy. Если у командлета/функции ConfirmImpact ниже (Low или Medium) запрос подтверждения по умолчанию выводиться не будет. Но возможно указать –Confirm для принудительного вывода запроса подтверждения. Если посмотреть на наш код, мы увидим, что уровень воздействия на систему высокий, поэтому мы каждый раз получаем запрос подтверждения. Если мы изменим уровень воздействия в уровень 'Medium', наша функция не будет по умолчанию выводить никаких запросов. Но будет выводить его при указании параметра –Confirm:

function Remove-File {
[CmdletBinding(
    ConfirmImpact = 'Medium',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Path,
        [switch]$Force
    )
    if ($Force -or $PSCmdlet.ShouldProcess($Path,"Delete file")) {
        Remove-Item $Path
    }
}

И вот какое у нас будет поведение:

[↓] [vPodans] Remove-File .\Desktop\qsca.txt -Confirm

Confirm
Are you sure you want to perform this action?
Performing operation "Delete file" on Target ".\Desktop\qsca.txt".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): n
[↓] [vPodans] Remove-File .\Desktop\qsca.txt
[↓] [vPodans]

Теперь запрос выводится только когда мы явно указываем параметр –Confirm. Параметры мы подключили, как реализовать код? Для этого есть специальная переменная $PsCmdlet, которая используется для обработки параметров командлета/функции. У этой переменной есть метод ShouldProcess("Target","Action description"). В аргумент Target указываете название объекта (в нашем случае — имя файла), над которым будет совершено действие, а в Action description пишите, какое действие будет произведено.

Указание параметра –WhatIf и/или –Confirm вызывает метод ShouldProcess и в зависимости от параметров выполняет нужное действие — выводит запрос или эмулирует выполнение команды. Поэтому мы просто засовываем $PSCmdlet.ShouldProcess() в условный оператор IF и в конструкции Then пишем код, который будет выполнять действие.

Чтобы реализовать функционал параметра –Force, я его поместил в то же условие. Причём, он должен проверяться первым. Это связано с тем, что при выполнении оператора –or, сначала проверяется выражение слева от оператора. Если оно возвращает True, выражение справа от оператора не проверяется. Следовательно, если параметр –Force указан, метод ShouldProcess() просто не вызовется.

Параметры Verbose и Debug

При распитиинаписании скриптов, весьма целесообразно включать отладочную информацию в скрипт, которую можно посмотреть, если что-то идёт не так. Запомните, НИКАКИХ этих ваших Write-Host или чего-то ещё. Для этого предусмотрены параметры Verbose и Debug. Verbose применяется для вывода на экран общего хода выполнения скрипта. Debug применяется для включения уже детальной отладочной информации и возможно переключение в пошаговое исполнение скрипта. Эти параметры не надо определять в параметрах функции, они автоматически добавляются к коду при использовании конструкции [CmdletBinding()].

Вот как правильно определять поведение этих параметров:

function Get-Something {
[CmdletBinding()]
    Param()
    if ($PSBoundParameters.Verbose) {$VerbosePreference = "Continue"}
    if ($PSBoundParameters.Debug) {$DebugPreference = "Continue"}
    Write-Verbose "Type some verbose information"
    Write-Debug "Type some debug information"
}

Как мы уже знаем, если указана конструкция [CmdletBinding()], эти параметры автоматически подключаются к функции и мы их не определяем в секции param(). Чтобы отловить эти параметры, мы используем специальную переменную $PSBoundParameters. Более подробней об этой переменной я писал в статье: Cmdlet wrapping и PsBoundParameters. По умолчанию, $VerbosePreference и $DebugPreference = 'SilentlyContinue', т.е. даже при указании этих параметров вы ничего не увидите. Поэтому, если параметр указан при вызове функции, мы переводим их в состояние 'Continue', что включает вывод для Verbose и Debug.

Давайте посмотрим более реальный случай в несферическом вакууме — сброс пароля локального администратора:

function Reset-LocalAdministratorPassword {
[CmdletBinding(
    ConfirmImpact = 'High',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Password,
        [switch]$Force
    )
    if ($Verbose) {$VerbosePreference = "Continue"}
    if ($Debug) {$DebugPreference = "Continue"}
    $Computer = $Env:COMPUTERNAME
    Write-Debug "Connecting to ADSI provider on '$Computer'"
    $winnt = [ADSI]("WinNT://$Computer,computer")
    Write-Debug "Attempting to retrieve local administrator object"
    $User = $winnt.psbase.children.Find("administrator")
    Write-Debug "Write new password for local administrator. New password '$Password'"
    $User.psbase.invoke("SetPassword",$Password)
    if ($Force -or $PsCmdlet.ShouldProcess("Administrator","Set new password for")) {
        Write-Verbose "Setting new password for local administrator on '$env:computername'"
        Write-Debug "Writing new password for local administrator on '$env:computername'"
        $User.psbase.CommitChanges()
    }
}

И вот вывод в консоли для –Verbose:

[↑] [system32] Reset-LocalAdministratorPassword -Password "P@ssw0rd" -Verbose

Confirm
Are you sure you want to perform this action?
Performing operation "Set new password for" on Target "Administrator".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):
VERBOSE: Setting new password for local administrator on 'THOR'
[↑] [system32]

для –Debug:

[↑] [system32] Reset-LocalAdministratorPassword -Password "P@ssw0rd" -Debug
DEBUG: Connecting to ADSI provider on 'THOR'

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):
DEBUG: Attempting to retrieve local administrator object

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):
DEBUG: Write new password for local administrator. New password 'P@ssw0rd'

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):

Confirm
Are you sure you want to perform this action?
Performing operation "Set new password for" on Target "Administrator".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):
DEBUG: Writing new password for local administrator on 'THOR'

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):
[↑] [system32]

Так же, мы видим, что Debug включает пошаговое исполнение скрипта, вне зависимоти от состояния параметра Force. Это только в случае, если у нас включен SupportsShouldProcess.

На сегодня вроде всё.


Share this article:

Comments:

Vadims Podāns

Пожалуйста. Если лень меня не подведёт, то это будет не последняя статья в данном цикле.

Comments are closed.